fix: key review checkmarks by file snapshots instead of revision pairs

This commit is contained in:
Valdemar Lorenzen
2026-10-20 11:09:48 +02:00
parent ea04e30072
commit 8f8d96ead9
9 changed files with 164 additions and 12 deletions
+100 -4
View File
@@ -787,6 +787,7 @@ fn comparison() -> Comparison {
editable_paths: BTreeSet::new(),
files: vec![
ChangedFile {
review_revisions: None,
path: "renamed.txt".into(),
old_path: Some("old.txt".into()),
status: FileStatus::Renamed,
@@ -824,6 +825,7 @@ fn comparison() -> Comparison {
notice: None,
},
ChangedFile {
review_revisions: None,
path: "added.txt".into(),
old_path: None,
status: FileStatus::Added,
@@ -841,6 +843,7 @@ fn comparison() -> Comparison {
notice: None,
},
ChangedFile {
review_revisions: None,
path: "removed.bin".into(),
old_path: None,
status: FileStatus::Removed,
@@ -1561,6 +1564,7 @@ fn closing_diff_rejects_pending_file_details_even_after_reopening() {
let detail = catalog.files.remove(0);
catalog.files.clear();
catalog.files.push(ChangedFile {
review_revisions: None,
path: detail.path.clone(),
old_path: detail.old_path.clone(),
status: detail.status,
@@ -1808,6 +1812,7 @@ fn live_state(before: Vec<String>, after: Vec<String>, eof: bool) -> DiffState {
after: "Working".into(),
editable_paths: BTreeSet::from([PathBuf::from("file")]),
files: vec![ChangedFile {
review_revisions: None,
path: "file".into(),
old_path: None,
status: FileStatus::Modified,
@@ -2164,10 +2169,12 @@ fn reviewed_files_survive_lazy_details_and_views_but_reset_on_comparison() {
}
#[test]
fn restored_review_marks_use_revision_pairs_and_rename_paths_not_catalog_order() {
fn restored_review_marks_use_file_identities_and_rename_paths_not_catalog_order() {
let mut state = DiffState::new();
state.request_kind = Some(RequestKind::Comparison);
state.apply_result(1, Ok(Outcome::Comparison(comparison())));
state.comparison.as_mut().unwrap().files[0].review_revisions =
Some(["file:before".into(), "file:after".into()]);
assert_eq!(state.toggle_reviewed(), Ok(true));
state.comparison.as_mut().unwrap().files.swap(0, 1);
state.restore_reviewed_files();
@@ -2177,17 +2184,104 @@ fn restored_review_marks_use_revision_pairs_and_rename_paths_not_catalog_order()
assert!(state.reviewed_files.is_empty());
state.comparison.as_mut().unwrap().files[1].old_path = Some("old.txt".into());
for side in [0, 1] {
let original = state.comparison.as_ref().unwrap().revisions[side].clone();
state.comparison.as_mut().unwrap().revisions[side].push_str("-different");
let identities = state.comparison.as_mut().unwrap().files[1]
.review_revisions
.as_mut()
.unwrap();
let original = identities[side].clone();
identities[side].push_str("-different");
state.restore_reviewed_files();
assert!(state.reviewed_files.is_empty());
state.comparison.as_mut().unwrap().revisions[side] = original;
state.comparison.as_mut().unwrap().files[1]
.review_revisions
.as_mut()
.unwrap()[side] = original;
}
state.comparison.as_mut().unwrap().files[1].loaded = false;
state.restore_reviewed_files();
assert_eq!(state.reviewed_files, BTreeSet::from([1]));
}
#[test]
fn unchanged_file_review_survives_other_files_changing() {
use crate::repository::Repository;
use std::sync::{Arc, atomic::AtomicBool};
let root = std::env::temp_dir().join(format!("vamp-review-files-{}", std::process::id()));
std::fs::create_dir(&root).unwrap();
let git = |args: &[&str]| {
let output = std::process::Command::new("git")
.current_dir(&root)
.args(args)
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_AUTHOR_DATE", "2000-01-01T00:00:00Z")
.env("GIT_COMMITTER_DATE", "2000-01-01T00:00:00Z")
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap().trim().to_owned()
};
git(&["init", "--initial-branch=local"]);
git(&["config", "user.name", "Review Test"]);
git(&["config", "user.email", "review@example.invalid"]);
for path in ["a.txt", "b.txt"] {
std::fs::write(root.join(path), "base\n").unwrap();
}
git(&["add", "."]);
git(&["commit", "-m", "base"]);
for path in ["a.txt", "b.txt"] {
std::fs::write(root.join(path), "changed\n").unwrap();
}
let before = revision(
RevisionTarget::Commit(git(&["rev-parse", "HEAD"])),
"base",
"",
Category::Commits,
);
let after = revision(RevisionTarget::Working, "Working", "", Category::Commits);
let mut repository = Repository::open(&root, Arc::new(AtomicBool::new(false))).unwrap();
let mut state = DiffState::new();
let changes = repository
.compare(1, &before, &after, ComparisonMode::Snapshots, &[])
.unwrap();
let original_revisions = changes.revisions.clone();
state.request_kind = Some(RequestKind::Comparison);
state.apply_result(1, Ok(Outcome::Comparison(changes)));
state.toggle_reviewed().unwrap();
state.file = 1;
state.focus = Focus::Before;
state.toggle_reviewed().unwrap();
let mut store = crate::review::Store::open(&root.join("reviews")).unwrap();
store.save(&mut state.local_review.document).unwrap();
let id = state.local_review.document.storage_id.unwrap();
std::fs::write(root.join("b.txt"), "edited again\n").unwrap();
let changes = repository
.compare(2, &before, &after, ComparisonMode::Snapshots, &[])
.unwrap();
assert_ne!(changes.revisions, original_revisions);
state.request_kind = Some(RequestKind::Comparison);
state.apply_result(2, Ok(Outcome::Comparison(changes)));
state.local_review.document = store.load(&root, id).unwrap();
state.restore_reviewed_files();
assert_eq!(state.reviewed_files, BTreeSet::from([0]));
let detail = repository.load_file(2, 0).unwrap();
state.apply_result(
3,
Ok(Outcome::File {
comparison_id: 2,
file_index: 0,
file: detail,
}),
);
state.restore_reviewed_files();
assert_eq!(state.reviewed_files, BTreeSet::from([0]));
std::fs::remove_dir_all(root).unwrap();
}
#[test]
fn restored_live_review_marks_require_exact_text_and_final_newline() {
let mut state = live_state(vec!["base".into()], vec!["changed".into()], false);
@@ -2405,6 +2499,7 @@ fn reverting_loaded_file_keeps_unloaded_changes_selectable() {
state.comparison_id = Some(17);
state.pending = None;
state.comparison.as_mut().unwrap().files.push(ChangedFile {
review_revisions: None,
path: "unloaded.txt".into(),
old_path: None,
status: FileStatus::Modified,
@@ -2632,6 +2727,7 @@ fn lazy_selection_waits_for_details_rejects_stale_files_and_reuses_loaded_cache(
let details: Vec<_> = catalog.files.drain(..).collect();
for detail in &details {
catalog.files.push(ChangedFile {
review_revisions: None,
path: detail.path.clone(),
old_path: detail.old_path.clone(),
status: detail.status,