perf: avoid eager diff loading and full-file typing work

This commit is contained in:
2026-10-11 15:06:05 +02:00
parent 7b4bc80538
commit 2e05725a38
11 changed files with 1881 additions and 386 deletions
+185 -64
View File
@@ -127,6 +127,26 @@ fn working(kind: RepoKind) -> Revision {
r.target = RevisionTarget::Working;
r
}
fn compare_loaded(
repository: &mut Repository,
before: &Revision,
after: &Revision,
buffers: &[(PathBuf, String)],
) -> Result<Comparison, String> {
let mut comparison = repository.compare(1, before, after, buffers)?;
for (index, file) in comparison.files.iter_mut().enumerate() {
*file = repository.load_file(1, index)?;
if file
.notice
.as_deref()
.is_some_and(|notice| notice.contains("Binary or non-UTF-8"))
{
comparison.editable_paths.remove(&file.path);
}
}
Ok(comparison)
}
fn changed<'a>(comparison: &'a Comparison, path: &Path) -> &'a vamp::diff::ChangedFile {
comparison
.files
@@ -236,9 +256,13 @@ fn git_working_reads_disk_and_handles_ranges_ignored_files_binary_links_and_odd_
assert!(catalog.entries.iter().any(|r| r.label == "nested"
&& r.category == Category::Tags
&& r.target == RevisionTarget::Commit(id.clone())));
let comparison = repository
.compare(&revision(&id, RepoKind::Git), &working(RepoKind::Git), &[])
.unwrap();
let comparison = compare_loaded(
&mut repository,
&revision(&id, RepoKind::Git),
&working(RepoKind::Git),
&[],
)
.unwrap();
let text = changed(&comparison, Path::new("text"));
assert_eq!(comparison.root, fs::canonicalize(&root).unwrap());
assert!(comparison.editable_paths.contains(Path::new("text")));
@@ -343,9 +367,13 @@ fn git_working_reads_disk_and_handles_ranges_ignored_files_binary_links_and_odd_
assert_eq!(moved.old_path.as_deref(), Some(Path::new("rename-old")));
assert_eq!(moved.before, moved.after);
assert!(moved.hunks.is_empty());
let reverse = repository
.compare(&working(RepoKind::Git), &revision(&id, RepoKind::Git), &[])
.unwrap();
let reverse = compare_loaded(
&mut repository,
&working(RepoKind::Git),
&revision(&id, RepoKind::Git),
&[],
)
.unwrap();
assert!(reverse.editable_paths.is_empty());
assert_eq!(
changed(&reverse, Path::new("added")).status,
@@ -353,7 +381,7 @@ fn git_working_reads_disk_and_handles_ranges_ignored_files_binary_links_and_odd_
);
assert!(
repository
.compare(&working(RepoKind::Git), &working(RepoKind::Git), &[])
.compare(1, &working(RepoKind::Git), &working(RepoKind::Git), &[])
.unwrap()
.files
.is_empty()
@@ -397,7 +425,7 @@ fn git_remote_refs_and_annotated_tags_are_discovered_before_fetch_and_history_st
);
let state = disk_state(&active);
let mut repository = open(&active);
let catalog = repository.catalog().unwrap();
let catalog = repository.list_remote("origin").unwrap();
let remote = catalog
.entries
.iter()
@@ -439,24 +467,23 @@ fn git_remote_refs_and_annotated_tags_are_discovered_before_fetch_and_history_st
.status
.success()
);
let comparison = repository
.compare(
&revision(&local, RepoKind::Git),
&remote,
&[(active.join("file"), "unsaved local text\n".into())],
)
.unwrap();
let comparison = compare_loaded(
&mut repository,
&revision(&local, RepoKind::Git),
&remote,
&[(active.join("file"), "unsaved local text\n".into())],
)
.unwrap();
assert!(comparison.editable_paths.is_empty());
assert_eq!(changed(&comparison, Path::new("file")).before, ["local"]);
assert_eq!(
changed(&comparison, Path::new("file")).after,
["remote tip"]
);
let comparison = repository.compare(&tag, &remote, &[]).unwrap();
let comparison = compare_loaded(&mut repository, &tag, &remote, &[]).unwrap();
assert_eq!(changed(&comparison, Path::new("file")).before, ["ancestor"]);
let comparison = repository
.compare(&remote, &working(RepoKind::Git), &[])
.unwrap();
let comparison =
compare_loaded(&mut repository, &remote, &working(RepoKind::Git), &[]).unwrap();
assert_eq!(changed(&comparison, Path::new("file")).after, ["local"]);
let loaded = repository.fetch_remote("origin").unwrap();
let historic = loaded
@@ -468,9 +495,13 @@ fn git_remote_refs_and_annotated_tags_are_discovered_before_fetch_and_history_st
.unwrap();
assert_eq!(historic.label, ancestor);
assert_eq!(historic.description, "remote ancestor needle");
let comparison = repository
.compare(historic, &revision(&local, RepoKind::Git), &[])
.unwrap();
let comparison = compare_loaded(
&mut repository,
historic,
&revision(&local, RepoKind::Git),
&[],
)
.unwrap();
assert_eq!(changed(&comparison, Path::new("file")).before, ["ancestor"]);
assert!(
!command("git", &active, &["cat-file", "-e", &tip])
@@ -541,9 +572,8 @@ fn jj_catalog_and_working_comparisons_prefer_jj_and_never_snapshot_or_merge_oper
.iter()
.any(|r| r.label == "baseline" && r.category == Category::Bookmarks)
);
let comparison = repository
.compare(baseline, &working(RepoKind::Jujutsu), &[])
.unwrap();
let comparison =
compare_loaded(&mut repository, baseline, &working(RepoKind::Jujutsu), &[]).unwrap();
assert_eq!(changed(&comparison, Path::new("file")).after, ["disk"]);
assert_eq!(
changed(&comparison, Path::new("deleted")).status,
@@ -644,7 +674,7 @@ fn jj_private_fetch_discovers_never_fetched_bookmark_and_searchable_remote_chang
fs::write(active.join("unsnapshotted"), "keep disk\n").unwrap();
let state = disk_state(&active);
let mut repository = open(&active);
let catalog = repository.catalog().unwrap();
let catalog = repository.list_remote("origin").unwrap();
let remote = catalog
.entries
.iter()
@@ -663,9 +693,8 @@ fn jj_private_fetch_discovers_never_fetched_bookmark_and_searchable_remote_chang
.iter()
.any(|r| r.target == RevisionTarget::Commit(ancestor.clone()))
);
let comparison = repository
.compare(&remote, &working(RepoKind::Jujutsu), &[])
.unwrap();
let comparison =
compare_loaded(&mut repository, &remote, &working(RepoKind::Jujutsu), &[]).unwrap();
assert_eq!(
changed(&comparison, Path::new("file")).before,
["remote tip"]
@@ -682,7 +711,7 @@ fn jj_private_fetch_discovers_never_fetched_bookmark_and_searchable_remote_chang
.unwrap();
assert!(historic.label.contains(&ancestor) && historic.label.contains(&change));
assert_eq!(historic.description, "remote historic needle");
let comparison = repository.compare(historic, &remote, &[]).unwrap();
let comparison = compare_loaded(&mut repository, historic, &remote, &[]).unwrap();
assert_eq!(
changed(&comparison, Path::new("file")).before,
["remote ancestor"]
@@ -735,7 +764,14 @@ fn errors_are_explicit_and_do_not_modify_active_repository() {
let state = disk_state(&root);
let mut repository = open(&root);
let catalog = repository.catalog().unwrap();
assert!(catalog.notice.unwrap().contains("broken"));
assert!(catalog.notice.is_none());
assert!(
catalog
.entries
.iter()
.any(|revision| revision.target == RevisionTarget::ListRemote("broken".into()))
);
assert!(repository.list_remote("broken").is_err());
assert!(repository.fetch_remote("broken").is_err());
assert!(
repository
@@ -746,6 +782,7 @@ fn errors_are_explicit_and_do_not_modify_active_repository() {
assert!(
repository
.compare(
1,
&revision("--bad", RepoKind::Git),
&working(RepoKind::Git),
&[]
@@ -756,6 +793,7 @@ fn errors_are_explicit_and_do_not_modify_active_repository() {
assert!(
repository
.compare(
1,
&revision(&"f".repeat(40), RepoKind::Git),
&working(RepoKind::Git),
&[],
@@ -769,7 +807,7 @@ fn errors_are_explicit_and_do_not_modify_active_repository() {
};
assert!(
repository
.compare(&loader, &revision(&id, RepoKind::Git), &[])
.compare(1, &loader, &revision(&id, RepoKind::Git), &[])
.unwrap_err()
.contains("history loader")
);
@@ -810,6 +848,7 @@ fn unchanged_submodules_are_not_false_changes_and_changed_pointers_are_explicit(
assert!(
repository
.compare(
1,
&revision(&baseline, RepoKind::Git),
&working(RepoKind::Git),
&[]
@@ -824,13 +863,13 @@ fn unchanged_submodules_are_not_false_changes_and_changed_pointers_are_explicit(
git(&root.join("nested"), &["fetch", "origin"]);
git(&root.join("nested"), &["checkout", "--detach", &second]);
let state = disk_state(&root);
let comparison = repository
.compare(
&revision(&baseline, RepoKind::Git),
&working(RepoKind::Git),
&[],
)
.unwrap();
let comparison = compare_loaded(
&mut repository,
&revision(&baseline, RepoKind::Git),
&working(RepoKind::Git),
&[],
)
.unwrap();
let submodule = changed(&comparison, Path::new("nested"));
assert_eq!(submodule.status, FileStatus::Modified);
let notice = submodule.notice.as_ref().unwrap();
@@ -897,13 +936,13 @@ fn multiple_remotes_retain_tag_only_history_even_when_tag_names_collide() {
&& r.description == format!("{name} tag-only needle"))
);
}
let comparison = repository
.compare(
&revision(&ids[0], RepoKind::Git),
&revision(&ids[1], RepoKind::Git),
&[],
)
.unwrap();
let comparison = compare_loaded(
&mut repository,
&revision(&ids[0], RepoKind::Git),
&revision(&ids[1], RepoKind::Git),
&[],
)
.unwrap();
assert_eq!(changed(&comparison, Path::new("file")).before, ["one"]);
assert_eq!(changed(&comparison, Path::new("file")).after, ["two"]);
assert_eq!(disk_state(&active), state);
@@ -1011,13 +1050,13 @@ fn jj_conflicted_revisions_show_cli_projection_and_keep_ordinary_files_navigable
.iter()
.any(|r| r.target == RevisionTarget::Commit(merged.clone()))
);
let comparison = repository
.compare(
&revision(&base, RepoKind::Jujutsu),
&revision(&merged, RepoKind::Jujutsu),
&[],
)
.unwrap();
let comparison = compare_loaded(
&mut repository,
&revision(&base, RepoKind::Jujutsu),
&revision(&merged, RepoKind::Jujutsu),
&[],
)
.unwrap();
let conflict = changed(&comparison, Path::new("conflict"));
assert_eq!(conflict.after, expected);
assert!(
@@ -1032,13 +1071,13 @@ fn jj_conflicted_revisions_show_cli_projection_and_keep_ordinary_files_navigable
assert_eq!(ordinary.before, ["old"]);
assert_eq!(ordinary.after, ["left ordinary"]);
assert!(ordinary.notice.is_none());
let working_comparison = repository
.compare(
&revision(&merged, RepoKind::Jujutsu),
&working(RepoKind::Jujutsu),
&[],
)
.unwrap();
let working_comparison = compare_loaded(
&mut repository,
&revision(&merged, RepoKind::Jujutsu),
&working(RepoKind::Jujutsu),
&[],
)
.unwrap();
assert!(
changed(&working_comparison, Path::new("conflict"))
.notice
@@ -1079,7 +1118,7 @@ fn unsaved_overlays_are_working_after_only_and_cannot_follow_nonregular_paths()
let mut repository = open(&root.join("sub"));
let base = revision(&id, RepoKind::Git);
let checkout = working(RepoKind::Git);
let comparison = repository.compare(&base, &checkout, &buffers).unwrap();
let comparison = compare_loaded(&mut repository, &base, &checkout, &buffers).unwrap();
assert_eq!(comparison.root, fs::canonicalize(&root).unwrap());
assert_eq!(changed(&comparison, Path::new("file")).after, ["unsaved"]);
assert!(comparison.editable_paths.contains(Path::new("file")));
@@ -1101,7 +1140,7 @@ fn unsaved_overlays_are_working_after_only_and_cannot_follow_nonregular_paths()
|| file.path == Path::new("missing")
|| file.path == Path::new("linked/file"))
);
let immutable = repository.compare(&checkout, &base, &buffers).unwrap();
let immutable = compare_loaded(&mut repository, &checkout, &base, &buffers).unwrap();
assert!(immutable.editable_paths.is_empty());
assert!(
!immutable
@@ -1111,12 +1150,12 @@ fn unsaved_overlays_are_working_after_only_and_cannot_follow_nonregular_paths()
);
assert!(
repository
.compare(&base, &base, &buffers)
.compare(1, &base, &base, &buffers)
.unwrap()
.files
.is_empty()
);
let same_checkout = repository.compare(&checkout, &checkout, &buffers).unwrap();
let same_checkout = compare_loaded(&mut repository, &checkout, &checkout, &buffers).unwrap();
assert_eq!(changed(&same_checkout, Path::new("file")).before, ["base"]);
assert_eq!(
changed(&same_checkout, Path::new("file")).after,
@@ -1128,3 +1167,85 @@ fn unsaved_overlays_are_working_after_only_and_cannot_follow_nonregular_paths()
"outside\n"
);
}
#[test]
fn immutable_catalog_skips_unchanged_blobs_and_prepares_only_requested_details() {
let fixture = Fixture::new();
let root = fixture.dir("lazy");
init_git(&root);
fs::write(root.join("unchanged-large"), vec![b'x'; 17 * 1024 * 1024]).unwrap();
fs::write(root.join("selected"), "before\n").unwrap();
fs::write(root.join("unselected"), "old\n").unwrap();
git(&root, &["add", "."]);
git(&root, &["commit", "-m", "before"]);
let before = git(&root, &["rev-parse", "HEAD"]);
fs::write(root.join("selected"), "after\n").unwrap();
fs::write(root.join("unselected"), vec![b'y'; 17 * 1024 * 1024]).unwrap();
git(&root, &["add", "."]);
git(&root, &["commit", "-m", "after"]);
let after = git(&root, &["rev-parse", "HEAD"]);
let mut repository = open(&root);
let comparison = repository
.compare(
100,
&revision(&before, RepoKind::Git),
&revision(&after, RepoKind::Git),
&[],
)
.unwrap();
assert_eq!(comparison.files.len(), 2);
assert!(comparison.files.iter().all(|file| !file.loaded
&& file.before.is_empty()
&& file.after.is_empty()
&& file.hunks.is_empty()));
let index = comparison
.files
.iter()
.position(|file| file.path == Path::new("selected"))
.unwrap();
let selected = repository.load_file(100, index).unwrap();
assert!(selected.loaded);
assert_eq!(selected.before, ["before"]);
assert_eq!(selected.after, ["after"]);
assert_eq!(selected.hunks.len(), 1);
assert!(repository.load_file(99, index).is_err());
let empty = repository
.compare(
101,
&revision(&after, RepoKind::Git),
&revision(&after, RepoKind::Git),
&[],
)
.unwrap();
assert!(empty.files.is_empty());
assert!(repository.load_file(100, index).is_err());
}
#[test]
fn deferred_file_details_keep_frozen_checkout_and_unsaved_after() {
let fixture = Fixture::new();
let root = fixture.dir("frozen").canonicalize().unwrap();
init_git(&root);
fs::write(root.join("file"), "committed\n").unwrap();
git(&root, &["add", "."]);
git(&root, &["commit", "-m", "before"]);
fs::write(root.join("file"), "disk before\n").unwrap();
let mut repository = open(&root);
let comparison = repository
.compare(
50,
&working(RepoKind::Git),
&working(RepoKind::Git),
&[(root.join("file"), "unsaved after\n".into())],
)
.unwrap();
assert_eq!(comparison.files.len(), 1);
assert!(!comparison.files[0].loaded);
fs::write(root.join("file"), "later disk\n").unwrap();
let details = repository.load_file(50, 0).unwrap();
assert_eq!(details.before, ["disk before"]);
assert_eq!(details.after, ["unsaved after"]);
let again = repository.load_file(50, 0).unwrap();
assert_eq!(again.before, details.before);
assert_eq!(again.after, details.after);
}