Validate original_hash in link_versions --reviewed; document follow-ups (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51600f900d
commit
aba6467cde
3 files changed
+67
-8
No files matched your search
@@ -179,6 +179,9 @@ fn main() -> anyhow::Result<()> {
|
||||
Arc::new(Mutex::new(Box::new(SqliteVersionDao::new())));
|
||||
let mut guard = dao.lock().unwrap();
|
||||
let report = backfill::apply(guard.as_mut(), &proposals, &reviewed, &rows);
|
||||
for why in &report.stale_details {
|
||||
eprintln!(" stale reviewed row refused: {why}");
|
||||
}
|
||||
let synced = guard
|
||||
.sync_version_metadata()
|
||||
.map_err(|e| anyhow::anyhow!("{e:?}"))?;
|
||||
|
||||
@@ -110,6 +110,8 @@ pub struct ReviewedLink {
|
||||
pub method: String,
|
||||
pub library_id: i32,
|
||||
pub version_path: String,
|
||||
/// The original's path as the dry run proposed it (may be empty).
|
||||
pub original_path: String,
|
||||
pub kind: String,
|
||||
pub version_hash: String,
|
||||
pub original_hash: String,
|
||||
@@ -140,6 +142,7 @@ pub fn parse_reviewed(tsv: &str) -> (Vec<ReviewedLink>, usize) {
|
||||
method: f[1].to_string(),
|
||||
library_id: f[2].parse().unwrap(),
|
||||
version_path: f[3].to_string(),
|
||||
original_path: f[4].to_string(),
|
||||
kind: f[6].to_string(),
|
||||
version_hash: f[7].to_string(),
|
||||
original_hash: f[8].to_string(),
|
||||
@@ -634,6 +637,8 @@ pub struct ApplyReport {
|
||||
pub refused: usize,
|
||||
/// Reviewed rows whose version row no longer matches the file in the TSV.
|
||||
pub stale: usize,
|
||||
/// One line per stale reviewed row, saying what no longer matches.
|
||||
pub stale_details: Vec<String>,
|
||||
}
|
||||
|
||||
/// Write the links. `exact` proposals apply as they are; `reviewed` come
|
||||
@@ -651,6 +656,8 @@ pub fn apply(
|
||||
.map(|r| ((r.library_id, r.rel_path.as_str()), r.content_hash.as_str()))
|
||||
.collect();
|
||||
|
||||
let live_hashes: HashSet<&str> = rows.iter().map(|r| r.content_hash.as_str()).collect();
|
||||
|
||||
let mut links: Vec<NewVersionLink> = Vec::new();
|
||||
for p in exact.iter().filter(|p| p.status == Status::Exact) {
|
||||
links.push(NewVersionLink {
|
||||
@@ -669,8 +676,32 @@ pub fn apply(
|
||||
});
|
||||
}
|
||||
for r in reviewed {
|
||||
if current.get(&(r.library_id, r.version_path.as_str())) != Some(&r.version_hash.as_str()) {
|
||||
let stale = if current.get(&(r.library_id, r.version_path.as_str()))
|
||||
!= Some(&r.version_hash.as_str())
|
||||
{
|
||||
Some("version file now holds different bytes (or is gone)")
|
||||
} else if !live_hashes.contains(r.original_hash.as_str()) {
|
||||
Some("original_hash is not in the index any more")
|
||||
} else if !r.original_path.is_empty()
|
||||
&& current
|
||||
.get(&(r.library_id, r.original_path.as_str()))
|
||||
.is_some_and(|h| *h != r.original_hash.as_str())
|
||||
{
|
||||
Some("original path now holds different bytes than original_hash")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(why) = stale {
|
||||
report.stale += 1;
|
||||
report.stale_details.push(format!(
|
||||
"{} -> {}: {why}",
|
||||
r.version_path,
|
||||
if r.original_path.is_empty() {
|
||||
&r.original_hash
|
||||
} else {
|
||||
&r.original_path
|
||||
}
|
||||
));
|
||||
continue;
|
||||
}
|
||||
links.push(NewVersionLink {
|
||||
@@ -977,6 +1008,19 @@ mod tests {
|
||||
stale_rows[2].content_hash = "changed".into();
|
||||
let report = apply(&mut dao, &[], &reviewed, &stale_rows);
|
||||
assert_eq!(report.stale, 1);
|
||||
|
||||
// So is one whose original changed since the dry run: its hash is
|
||||
// gone from the index, or its path holds other bytes.
|
||||
let mut gone = rows.clone();
|
||||
gone[0].content_hash = "ha-edited".into();
|
||||
let report = apply(&mut dao, &[], &reviewed, &gone);
|
||||
assert_eq!(report.stale, 1);
|
||||
assert_eq!(report.linked + report.already_linked, 0);
|
||||
assert!(report.stale_details[0].contains("d/aiedits/o_00001_.png"));
|
||||
let mut swapped = rows.clone();
|
||||
swapped[0].content_hash = "hb".into(); // d/a.jpg now holds hb's bytes
|
||||
let report = apply(&mut dao, &[], &reviewed, &swapped);
|
||||
assert_eq!(report.stale, 1);
|
||||
}
|
||||
#[test]
|
||||
fn apply_drops_derived_data_of_an_already_ingested_version() {
|
||||
@@ -1000,11 +1044,13 @@ mod tests {
|
||||
method: "phash".into(),
|
||||
library_id: 1,
|
||||
version_path: "v.png".into(),
|
||||
original_path: "o.jpg".into(),
|
||||
kind: "edit".into(),
|
||||
version_hash: "v".into(),
|
||||
original_hash: "o".into(),
|
||||
references: vec![],
|
||||
}];
|
||||
let rows = vec![row("v.png", "v", None), row("o.jpg", "o", None)];
|
||||
assert_eq!(apply(&mut dao, &[], &reviewed, &rows).linked, 1);
|
||||
|
||||
#[derive(diesel::QueryableByName)]
|
||||
|
||||
Reference in new issue
Block a user