fix: show revision-aware blame in diff buffers

Diff and snapshot sources were excluded before scheduling, and the separate diff renderer never painted attribution. Capture per-side Git provenance, preserve private object stores for historical tabs, and reuse the current-line worker and cache.
This commit is contained in:
Valdemar Lorenzen
2026-10-20 11:47:49 +02:00
parent 8f8d96ead9
commit 4fb1be7972
16 changed files with 819 additions and 91 deletions
+71 -1
View File
@@ -226,6 +226,13 @@ fn comparison_revisions_freeze_commits_and_address_working_contents() {
.unwrap();
assert_eq!(first.revisions[0], id);
assert!(first.revisions[1].starts_with("working:"));
assert_eq!(first.blame[0].as_ref().unwrap().commit, id);
assert_eq!(
first.blame[1]
.as_ref()
.map(|baseline| baseline.commit.as_str()),
(kind == RepoKind::Git).then_some(id.as_str())
);
let reopened = open(&root)
.compare(2, &before, &after, ComparisonMode::Snapshots, &[])
.unwrap();
@@ -255,6 +262,10 @@ fn comparison_revisions_freeze_commits_and_address_working_contents() {
.unwrap();
assert_eq!(working_pair.revisions[0], first.revisions[1]);
assert_eq!(working_pair.revisions[0], working_pair.revisions[1]);
assert_eq!(
working_pair.blame,
[first.blame[1].clone(), first.blame[1].clone()]
);
assert_eq!(disk_state(&root), state);
fs::write(root.join("file"), "working").unwrap();
let no_newline = repository
@@ -785,6 +796,8 @@ fn jj_bookmark_refresh_tracks_movement_and_same_change_rewrites_without_touching
panic!("Bookmark movement must replace contents even when follow is disabled");
};
assert_eq!(comparison.after, topic.label);
assert!(comparison.blame[0].is_none());
assert_eq!(comparison.blame[1].as_ref().unwrap().commit, next);
assert_eq!(repository.load_file(2, 0).unwrap().after, ["next"]);
assert!(repository.load_file(1, 0).is_err());
assert!(
@@ -944,6 +957,8 @@ fn jj_bookmark_refresh_recomputes_branch_merge_base_and_recovers_after_deletion(
.unwrap();
assert!(original.before.ends_with(&base));
assert_eq!(original.revisions, [base.clone(), tip.clone()]);
assert_eq!(original.blame[0].as_ref().unwrap().commit, base);
assert_eq!(original.blame[1].as_ref().unwrap().commit, tip);
jj(&root, &["bookmark", "set", "before", "-r", &tip]);
let Some(CheckoutRefresh::Bookmark(comparison)) =
repository.refresh_checkout(1, 2, false, &[]).unwrap()
@@ -952,6 +967,8 @@ fn jj_bookmark_refresh_recomputes_branch_merge_base_and_recovers_after_deletion(
};
assert!(comparison.before.ends_with(&tip));
assert_eq!(comparison.revisions, [tip.clone(), tip.clone()]);
assert_eq!(comparison.blame[0].as_ref().unwrap().commit, tip);
assert_eq!(comparison.blame[1].as_ref().unwrap().commit, tip);
assert!(comparison.files.is_empty());
jj(&root, &["bookmark", "delete", "after"]);
let state = disk_state(&root);
@@ -1331,6 +1348,7 @@ fn jj_private_fetch_discovers_never_fetched_bookmark_and_searchable_remote_chang
.unwrap();
assert_eq!(promoted.before, branch.before);
assert_eq!(promoted.after, branch.after);
assert_eq!(promoted.blame, branch.blame);
assert!(promoted.editable_paths.contains(Path::new("file")));
assert_eq!(repository.load_file(62, 0).unwrap().after, ["remote tip"]);
assert_eq!(disk_state(&active), checked_out_state);
@@ -2260,6 +2278,8 @@ fn git_working_after_advances_only_to_descendants_with_new_paths_and_unsaved_tex
.unwrap();
assert_eq!(refreshed.before, original.before);
assert_eq!(refreshed.after, original.after);
assert_eq!(refreshed.blame[0], original.blame[0]);
assert_eq!(refreshed.blame[1].as_ref().unwrap().commit, tip);
for (index, file) in refreshed.files.iter_mut().enumerate() {
*file = repository.load_file(2, index).unwrap();
}
@@ -2526,13 +2546,14 @@ fn jj_working_after_follows_changes_not_rewrites_and_rejects_other_history() {
jj(&root, &["new"]);
fs::write(root.join("file"), "original disk\n").unwrap();
let mut repository = open(&root);
compare_loaded(
let initial = compare_loaded(
&mut repository,
&revision(&base, RepoKind::Jujutsu),
&working(RepoKind::Jujutsu),
&[],
)
.unwrap();
assert_eq!(initial.blame[1].as_ref().unwrap().commit, base);
fs::write(root.join("file"), "same-change rewrite\n").unwrap();
jj(&root, &["describe", "-m", "rewritten"]);
let state = disk_state(&root);
@@ -2545,6 +2566,7 @@ fn jj_working_after_follows_changes_not_rewrites_and_rejects_other_history() {
assert_eq!(repository.load_file(1, 0).unwrap().after, ["original disk"]);
assert_eq!(disk_state(&root), state);
jj(&root, &["new"]);
let parent = jj(&root, &["log", "--no-graph", "-r", "@-", "-T", "commit_id"]);
fs::remove_file(root.join("removed")).unwrap();
fs::write(root.join("added"), "new file\n").unwrap();
let state = disk_state(&root);
@@ -2553,6 +2575,8 @@ fn jj_working_after_follows_changes_not_rewrites_and_rejects_other_history() {
.unwrap()
.map(checkout_comparison)
.unwrap();
assert_eq!(comparison.blame[0], initial.blame[0]);
assert_eq!(comparison.blame[1].as_ref().unwrap().commit, parent);
for (index, file) in comparison.files.iter_mut().enumerate() {
*file = repository.load_file(2, index).unwrap();
}
@@ -2727,12 +2751,16 @@ fn branch_changes_resolves_local_and_private_remote_ancestry_without_active_muta
assert_eq!(branch.files.len(), 1);
assert_eq!(branch.files[0].path, Path::new("remote-only"));
assert!(branch.before.ends_with(&base));
assert_eq!(branch.blame[0].as_ref().unwrap().commit, base);
assert!(!branch.blame[1].as_ref().unwrap().store.starts_with(&active));
assert_eq!(repository.load_file(40, 0).unwrap().after, ["remote"]);
let reverse = repository
.compare(41, remote, &local, ComparisonMode::BranchChanges, &[])
.unwrap();
assert_eq!(reverse.files.len(), 1);
assert_eq!(reverse.files[0].path, Path::new("local-only"));
assert_eq!(reverse.blame[0].as_ref().unwrap().commit, base);
assert_eq!(reverse.blame[1].as_ref().unwrap().commit, local.label);
assert_eq!(repository.load_file(41, 0).unwrap().after, ["local"]);
assert!(
repository
@@ -2866,3 +2894,45 @@ fn pull_request_refs_fetch_pinned_fork_heads_privately_for_git_and_jj() {
assert_eq!(disk_state(&root), state);
}
}
#[test]
fn jj_working_blame_captures_one_parent_and_never_selects_an_arbitrary_merge_parent() {
let fixture = Fixture::new();
let root = fixture.dir("blame-parents");
init_jj(&root, false);
fs::write(root.join("file"), "base\n").unwrap();
jj(&root, &["describe", "-m", "base"]);
let base = jj(&root, &["log", "--no-graph", "-r", "@", "-T", "commit_id"]);
jj(&root, &["new"]);
let state = disk_state(&root);
let mut repository = open(&root);
let one_parent = repository
.compare(
1,
&working(RepoKind::Jujutsu),
&working(RepoKind::Jujutsu),
ComparisonMode::Snapshots,
&[],
)
.unwrap();
assert_eq!(one_parent.blame[0].as_ref().unwrap().commit, base);
assert_eq!(one_parent.blame[0], one_parent.blame[1]);
assert_eq!(disk_state(&root), state);
jj(&root, &["new", "root()"]);
fs::write(root.join("other"), "sibling\n").unwrap();
jj(&root, &["describe", "-m", "sibling"]);
let sibling = jj(&root, &["log", "--no-graph", "-r", "@", "-T", "commit_id"]);
jj(&root, &["new", &base, &sibling]);
let state = disk_state(&root);
let merged = repository
.compare(
2,
&working(RepoKind::Jujutsu),
&working(RepoKind::Jujutsu),
ComparisonMode::Snapshots,
&[],
)
.unwrap();
assert_eq!(merged.blame, [None, None]);
assert_eq!(disk_state(&root), state);
}