feat: default diffs to branch changes
This commit is contained in:
+88
-12
@@ -1,7 +1,7 @@
|
||||
//! Read-only active-repository discovery and isolated revision comparison.
|
||||
use crate::diff::{
|
||||
Catalog, Category, ChangedFile, Comparison, FileStatus, RepoKind, Revision, RevisionTarget,
|
||||
line_hunks,
|
||||
Catalog, Category, ChangedFile, Comparison, ComparisonMode, FileStatus, RepoKind, Revision,
|
||||
RevisionTarget, line_hunks,
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
@@ -664,6 +664,27 @@ impl Repository {
|
||||
checked(run(cmd, vec![], &self.cancelled)?)?;
|
||||
self.private = Some((root.clone(), root));
|
||||
}
|
||||
// Only the private store gains alternates; active refs and objects stay untouched.
|
||||
let objects = self.git(
|
||||
&self.store,
|
||||
&[
|
||||
"rev-parse",
|
||||
"--path-format=absolute",
|
||||
"--git-path",
|
||||
"objects",
|
||||
],
|
||||
)?;
|
||||
let mut alternate = vec![b'"'];
|
||||
for &byte in objects.strip_suffix(b"\n").unwrap_or(&objects) {
|
||||
if (b' '..=b'~').contains(&byte) && !matches!(byte, b'"' | b'\\') {
|
||||
alternate.push(byte);
|
||||
} else {
|
||||
write!(&mut alternate, "\\{byte:03o}").unwrap();
|
||||
}
|
||||
}
|
||||
alternate.extend_from_slice(b"\"\n");
|
||||
let store = &self.private.as_ref().unwrap().1;
|
||||
fs::write(store.join("objects/info/alternates"), alternate).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
fn load_remote(&mut self, name: &str, reference: Option<&str>) -> Result<(), String> {
|
||||
@@ -681,10 +702,7 @@ impl Repository {
|
||||
let (root, store) = self.private.as_ref().unwrap();
|
||||
if self.kind == RepoKind::Jujutsu {
|
||||
if !self.registered.contains(name) {
|
||||
self.jj(
|
||||
root,
|
||||
&["git", "remote", "add", "--fetch-tags=all", "--", name, &url],
|
||||
)?;
|
||||
self.jj(root, &["git", "remote", "add", "--", name, &url])?;
|
||||
self.registered.insert(name.into());
|
||||
}
|
||||
self.jj(
|
||||
@@ -1279,6 +1297,40 @@ impl Repository {
|
||||
self.commit_snapshot(&store, &id)
|
||||
}
|
||||
}
|
||||
fn merge_base(&mut self, before: &str, after: &str) -> Result<String, String> {
|
||||
const JJ_ROOT: &str = "0000000000000000000000000000000000000000";
|
||||
if self.kind == RepoKind::Jujutsu && (before == JJ_ROOT || after == JJ_ROOT) {
|
||||
return Ok(JJ_ROOT.into());
|
||||
}
|
||||
let store = self
|
||||
.private
|
||||
.as_ref()
|
||||
.map_or(&self.store, |(_, store)| store);
|
||||
let mut command = git_command(store);
|
||||
command.args(["merge-base", "--all", before, after]);
|
||||
let output = run(command, vec![], &self.cancelled)?;
|
||||
if output.code == Some(1) && output.stdout.is_empty() {
|
||||
// Every jj history has the synthetic root, absent from Git's graph.
|
||||
return if self.kind == RepoKind::Jujutsu {
|
||||
Ok(JJ_ROOT.into())
|
||||
} else {
|
||||
Err(
|
||||
"Branch changes: no merge base; use Snapshots to compare unrelated histories"
|
||||
.into(),
|
||||
)
|
||||
};
|
||||
}
|
||||
let bases = text(checked(output).map_err(|e| format!("Cannot resolve merge base: {e}"))?)?;
|
||||
let mut bases = bases.lines();
|
||||
let base = bases.next().ok_or("Branch changes: no merge base")?;
|
||||
if bases.next().is_some() {
|
||||
return Err(
|
||||
"Branch changes: multiple merge bases; select Snapshots or different endpoints"
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
Ok(base.into())
|
||||
}
|
||||
/// Freeze comparison sources and return path/status metadata only.
|
||||
/// A new comparison replaces sources retained for the previous request ID.
|
||||
pub fn compare(
|
||||
@@ -1286,19 +1338,43 @@ impl Repository {
|
||||
comparison_id: u64,
|
||||
before: &Revision,
|
||||
after: &Revision,
|
||||
mode: ComparisonMode,
|
||||
buffers: &[(PathBuf, String)],
|
||||
) -> Result<Comparison, String> {
|
||||
self.comparison = None;
|
||||
let mut left = self.snapshot(&before.target)?;
|
||||
let working_after = after.target == RevisionTarget::Working;
|
||||
let mut right = if before.target == after.target && (!working_after || buffers.is_empty()) {
|
||||
let resolved = if mode == ComparisonMode::BranchChanges
|
||||
&& before.target != RevisionTarget::Working
|
||||
&& after.target != RevisionTarget::Working
|
||||
{
|
||||
let (_, before_id) = self.resolve(&before.target)?;
|
||||
let (_, after_id) = self.resolve(&after.target)?;
|
||||
let base = self.merge_base(&before_id, &after_id)?;
|
||||
let label = format!(
|
||||
"merge base of {} and {} · {base}",
|
||||
before.label, after.label
|
||||
);
|
||||
Some((
|
||||
RevisionTarget::Commit(base),
|
||||
RevisionTarget::Commit(after_id),
|
||||
label,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (before_target, after_target, before_label) = resolved.as_ref().map_or(
|
||||
(&before.target, &after.target, before.label.as_str()),
|
||||
|(before, after, label)| (before, after, label.as_str()),
|
||||
);
|
||||
let mut left = self.snapshot(before_target)?;
|
||||
let working_after = *after_target == RevisionTarget::Working;
|
||||
let same = before_target == after_target && (!working_after || buffers.is_empty());
|
||||
let mut right = if same {
|
||||
BTreeMap::new()
|
||||
} else if working_after {
|
||||
self.working_snapshot(buffers)?
|
||||
} else {
|
||||
self.snapshot(&after.target)?
|
||||
self.snapshot(after_target)?
|
||||
};
|
||||
let same = before.target == after.target && (!working_after || buffers.is_empty());
|
||||
// Frozen checkout/conflict bytes have no object identity. Compare them in a
|
||||
// single batch, while immutable tree/tree comparisons need only object IDs.
|
||||
let frozen = |file: &FileData| {
|
||||
@@ -1389,7 +1465,7 @@ impl Repository {
|
||||
.collect(),
|
||||
});
|
||||
Ok(Comparison {
|
||||
before: before.label.clone(),
|
||||
before: before_label.into(),
|
||||
after: after.label.clone(),
|
||||
files,
|
||||
root: self.root.clone(),
|
||||
|
||||
Reference in New Issue
Block a user