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
+70 -5
View File
@@ -1,7 +1,7 @@
//! Read-only active-repository discovery and isolated revision comparison.
use crate::diff::{
Catalog, Category, ChangedFile, Comparison, ComparisonMode, FileStatus, PullRequestPage,
RepoKind, Revision, RevisionTarget, line_hunks,
BlameRevision, Catalog, Category, ChangedFile, Comparison, ComparisonMode, FileStatus,
PullRequestPage, RepoKind, Revision, RevisionTarget, line_hunks,
};
use std::{
collections::{BTreeMap, BTreeSet},
@@ -28,7 +28,8 @@ const WORKING_SNAPSHOT_LIMIT: usize = 512 * 1024 * 1024;
const TIMEOUT: Duration = Duration::from_secs(60);
static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
struct Temporary(PathBuf);
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Temporary(PathBuf);
impl Temporary {
fn new() -> Result<Self, String> {
for _ in 0..100 {
@@ -1054,6 +1055,7 @@ struct PreparedComparison {
selected: [Revision; 2],
selected_ids: [Option<String>; 2],
revisions: [String; 2],
blame: [Option<BlameRevision>; 2],
mode: ComparisonMode,
left: Snapshot,
right: Snapshot,
@@ -1077,7 +1079,7 @@ pub struct Repository {
root: PathBuf,
kind: RepoKind,
store: PathBuf,
temporary: Temporary,
temporary: Arc<Temporary>,
private: Option<(PathBuf, PathBuf)>, // workspace and Git object store
remotes: Vec<Remote>,
remote_refs: BTreeMap<(String, String), String>,
@@ -1128,7 +1130,7 @@ impl Repository {
root,
kind,
store,
temporary: Temporary::new()?,
temporary: Arc::new(Temporary::new()?),
private: None,
remotes: vec![],
remote_refs: BTreeMap::new(),
@@ -2390,6 +2392,48 @@ impl Repository {
}
Ok(Some(text(checked(output)?)?.trim().into()))
}
fn blame_baseline(&mut self, target: &RevisionTarget) -> Result<Option<BlameRevision>, String> {
let source = if *target == RevisionTarget::Working {
let id = if self.kind == RepoKind::Git {
self.working_anchor()?
} else {
let parents = text(self.jj(
&self.root,
&[
"log",
"--no-graph",
"-r",
"@-",
"-T",
"commit_id ++ \"\\n\"",
],
)?)?;
let mut parents = parents.lines();
let parent = parents.next();
// A merge has no single attribution baseline.
if parents.next().is_some() {
None
} else {
parent.map(str::to_owned)
}
};
id.map(|id| (self.store.clone(), id))
} else {
Some(self.resolve(target)?)
};
Ok(source
.filter(|(_, id)| !id.bytes().all(|byte| byte == b'0'))
.map(|(store, commit)| BlameRevision {
keepalive: self
.private
.as_ref()
.filter(|(_, private_store)| private_store == &store)
.map(|_| Arc::clone(&self.temporary)),
store,
commit,
}))
}
fn working_advanced(&self, before: Option<&str>, after: Option<&str>) -> Result<bool, String> {
if before == after || after.is_none() {
return Ok(false);
@@ -2492,11 +2536,16 @@ impl Repository {
} else {
self.snapshot_revision(after_target, &right)?
};
let blame = [
self.blame_baseline(before_target)?,
self.blame_baseline(after_target)?,
];
let mut prepared = PreparedComparison {
id: comparison_id,
selected: [before.clone(), after.clone()],
selected_ids,
revisions: [before_revision, after_revision],
blame,
mode,
left,
right,
@@ -2614,10 +2663,12 @@ impl Repository {
}
let right = self.working_snapshot(buffers)?;
let revision = self.snapshot_revision(&RevisionTarget::Working, &right)?;
let blame = self.blame_baseline(&RevisionTarget::Working)?;
let mut prepared = self.comparison.take().unwrap();
let previous_right = std::mem::replace(&mut prepared.right, right);
let previous_files = std::mem::take(&mut prepared.files);
let previous_revision = std::mem::replace(&mut prepared.revisions[1], revision);
let previous_blame = std::mem::replace(&mut prepared.blame[1], blame);
let result = self
.build_comparison(&mut prepared, false)
.and_then(|comparison| {
@@ -2640,6 +2691,7 @@ impl Repository {
prepared.right = previous_right;
prepared.files = previous_files;
prepared.revisions[1] = previous_revision;
prepared.blame[1] = previous_blame;
}
self.comparison = Some(prepared);
result.map(|comparison| Some(CheckoutRefresh::Checkout(comparison)))
@@ -2671,13 +2723,16 @@ impl Repository {
&& follow_working
&& self.working_advanced(prepared.working_anchor.as_deref(), anchor.as_deref())?;
let mut revisions = prepared.revisions.clone();
let mut blame = prepared.blame.clone();
let (left, before_label) = if branch {
let base = self.merge_base(ids[0].as_deref().unwrap(), ids[1].as_deref().unwrap())?;
let label = format!("merge base of {label} and {after_label} · {base}");
revisions[0] = base.clone();
blame[0] = self.blame_baseline(&RevisionTarget::Commit(base.clone()))?;
(Some(self.snapshot(&RevisionTarget::Commit(base))?), label)
} else if changed_before {
revisions[0] = ids[0].clone().unwrap();
blame[0] = self.blame_baseline(&RevisionTarget::Commit(revisions[0].clone()))?;
(
Some(self.snapshot(&RevisionTarget::Commit(ids[0].clone().unwrap()))?),
label,
@@ -2699,6 +2754,13 @@ impl Repository {
ids[1].clone().unwrap()
};
}
if right.is_some() {
blame[1] = self.blame_baseline(&if advance_working {
RevisionTarget::Working
} else {
RevisionTarget::Commit(revisions[1].clone())
})?;
}
let pinned_checkout = ids[1]
.as_deref()
.map(|id| self.pinned_checkout(id))
@@ -2711,6 +2773,7 @@ impl Repository {
let previous_before = std::mem::replace(&mut prepared.before, before_label);
let previous_editable = prepared.editable_after;
let previous_revisions = std::mem::replace(&mut prepared.revisions, revisions);
let previous_blame = std::mem::replace(&mut prepared.blame, blame);
prepared.editable_after =
working_after || (pinned_checkout.is_some() && pinned_checkout == anchor);
let result = self
@@ -2747,6 +2810,7 @@ impl Repository {
prepared.before = previous_before;
prepared.editable_after = previous_editable;
prepared.revisions = previous_revisions;
prepared.blame = previous_blame;
}
self.comparison = Some(prepared);
result
@@ -2861,6 +2925,7 @@ impl Repository {
};
Comparison {
revisions: prepared.revisions.clone(),
blame: prepared.blame.clone(),
before: prepared.before.clone(),
after: prepared.after.clone(),
files,