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,
+7
View File
@@ -1885,6 +1885,7 @@ fn hidden_tab_diff_results_restore_visible_insert_interaction_and_route_request_
after: "working".into(),
editable_paths: Default::default(),
files: vec![ChangedFile {
review_revisions: None,
loaded: true,
path: "file.txt".into(),
old_path: None,
@@ -3050,6 +3051,7 @@ fn working_diff(editor: &mut Editor, root: &Path, paths: &[&str]) -> Box<[(PathB
let text = fs::read_to_string(root.join(path)).unwrap();
let lines: Vec<String> = text.lines().map(str::to_owned).collect();
ChangedFile {
review_revisions: None,
loaded: true,
path: PathBuf::from(*path),
old_path: None,
@@ -3109,6 +3111,7 @@ fn historical_diff(editor: &mut Editor, root: &Path, before: Vec<String>, after:
after: "head".into(),
editable_paths: Default::default(),
files: vec![ChangedFile {
review_revisions: None,
loaded: true,
path: "file.txt".into(),
old_path: None,
@@ -4071,6 +4074,7 @@ fn bookmark_refresh_reloads_paths_and_preserves_view_until_lazy_content_arrives(
let old_snapshot = editor.buffer_id();
let cursor = editor.cursor();
let file = |path: &str, loaded| ChangedFile {
review_revisions: None,
path: path.into(),
old_path: None,
status: FileStatus::Modified,
@@ -4278,6 +4282,7 @@ fn completed_working_refresh_serializes_lazy_selection_and_invalidates_pending_n
}
if changed {
let file = crate::diff::ChangedFile {
review_revisions: None,
path: "nested/new.txt".into(),
old_path: Some("nested/old.txt".into()),
status: crate::diff::FileStatus::Renamed,
@@ -4318,6 +4323,7 @@ fn lazy_lsp_target(editor: &mut Editor) -> crate::diff::ChangedFile {
let diff = editor.diff.as_mut().unwrap();
let comparison = diff.comparison.as_mut().unwrap();
comparison.files.push(ChangedFile {
review_revisions: None,
path: "nested/new.txt".into(),
old_path: Some("nested/old.txt".into()),
status: FileStatus::Renamed,
@@ -4338,6 +4344,7 @@ fn lazy_lsp_target(editor: &mut Editor) -> crate::diff::ChangedFile {
.map(|(index, file)| (index, file.path.clone())),
));
ChangedFile {
review_revisions: None,
path: "nested/new.txt".into(),
old_path: Some("nested/old.txt".into()),
status: FileStatus::Renamed,
+1
View File
@@ -3727,6 +3727,7 @@ fn async_responses_edit_only_current_bound_working_after_and_refresh_projection(
after: "Working".into(),
editable_paths: [PathBuf::from("buffer.txt")].into_iter().collect(),
files: vec![ChangedFile {
review_revisions: None,
path: PathBuf::from("buffer.txt"),
old_path: None,
status: FileStatus::Modified,
+9
View File
@@ -1984,6 +1984,7 @@ fn syntax_diff_file(path: &str, before: &str, after: &str) -> ChangedFile {
let before: Vec<String> = before.split('\n').map(str::to_owned).collect();
let after: Vec<String> = after.split('\n').map(str::to_owned).collect();
ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,
@@ -2605,6 +2606,7 @@ fn diff_comparison(files: Vec<ChangedFile>) -> DiffState {
fn deletion_file() -> ChangedFile {
ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,
@@ -3555,6 +3557,7 @@ fn diff_excerpts_render_original_lines_and_expand_without_losing_changes() {
after[30] = "let first_change = 900;".into();
after[60] = "let second_change = 901;".into();
let mut diff = diff_comparison(vec![ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,
@@ -3694,6 +3697,7 @@ fn diff_row_tint_covers_blank_lines_and_wrapped_gutters() {
#[test]
fn diff_added_rows_hatch_missing_before_but_tint_real_empty_after_lines() {
let mut diff = diff_comparison(vec![ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,
@@ -3751,6 +3755,7 @@ fn diff_added_rows_hatch_missing_before_but_tint_real_empty_after_lines() {
#[test]
fn diff_changed_backgrounds_preserve_syntax_and_clipped_source_cells() {
let mut diff = diff_comparison(vec![ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,
@@ -3880,6 +3885,7 @@ fn diff_changed_backgrounds_preserve_syntax_and_clipped_source_cells() {
#[test]
fn diff_view_changes_files_focus_and_keeps_horizontal_offsets_independent() {
let added = ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,
@@ -4641,6 +4647,7 @@ fn large_generated_additions_render_first_scroll_and_final_rows_without_reparsin
.collect::<Vec<_>>();
let hunks = crate::diff::line_hunks(&[], false, &after, true);
let mut diff = diff_comparison(vec![ChangedFile {
review_revisions: None,
path: "parser.c".into(),
old_path: None,
status: FileStatus::Added,
@@ -4690,6 +4697,7 @@ fn diff_notices_empty_comparisons_and_tiny_layouts_remain_renderable() {
assert!(screen_text(&terminal).contains("No changed files"));
assert!(screen_text(&terminal).contains("Explorer · Tree"));
let binary = diff_comparison(vec![ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,
@@ -4704,6 +4712,7 @@ fn diff_notices_empty_comparisons_and_tiny_layouts_remain_renderable() {
render_diff(&mut terminal, &binary, &mut panels);
assert!(screen_text(&terminal).contains("Binary file"));
let blank = diff_comparison(vec![ChangedFile {
review_revisions: None,
loaded: true,
before_eof: false,
after_eof: false,