fix: preserve lazy snapshots for conflicted jj revisions

Conflicted revisions read every file through jj file show, which returns
empty output for symlinks and causes repository-wide subprocess fanout.
Keep ordinary Git blob sources and immutable identities; render only
selected conflicted paths through jj.

Verified all 905 tests and the original local bookmark comparison with
25 correct paths in 0.522s, plus actual terminal conflict viewing.
Strict Clippy is blocked by existing diagnostics outside this change.
This commit is contained in:
Valdemar Lorenzen
2026-10-14 15:32:52 +02:00
parent f26237af78
commit dcfa50319c
5 changed files with 168 additions and 61 deletions
+96
View File
@@ -1053,6 +1053,7 @@ fn jj_conflicted_revisions_show_cli_projection_and_keep_ordinary_files_navigable
init_jj(&root, false);
fs::write(root.join("conflict"), "base\n").unwrap();
fs::write(root.join("ordinary"), "old\n").unwrap();
symlink("ordinary", root.join("unchanged-link")).unwrap();
jj(&root, &["describe", "-m", "base"]);
let base = jj(
&root,
@@ -1155,6 +1156,14 @@ fn jj_conflicted_revisions_show_cli_projection_and_keep_ordinary_files_navigable
&[],
)
.unwrap();
assert_eq!(
comparison
.files
.iter()
.map(|file| file.path.as_path())
.collect::<Vec<_>>(),
[Path::new("conflict"), Path::new("ordinary")]
);
let conflict = changed(&comparison, Path::new("conflict"));
assert_eq!(conflict.after, expected);
assert!(
@@ -1209,6 +1218,93 @@ fn jj_conflicted_revisions_show_cli_projection_and_keep_ordinary_files_navigable
assert_eq!(disk_state(&root), state);
}
#[test]
fn conflicted_catalog_preserves_symlinks_and_defers_unselected_contents() {
let fixture = Fixture::new();
let root = fixture.dir("lazy-conflict");
init_jj(&root, false);
fs::write(root.join("conflict"), "base\n").unwrap();
fs::write(root.join("unselected"), "old\n").unwrap();
symlink("unselected", root.join("unchanged-link")).unwrap();
jj(&root, &["describe", "-m", "base"]);
let base = jj(&root, &["log", "--no-graph", "-r", "@", "-T", "commit_id"]);
jj(&root, &["new", &base]);
fs::write(root.join("conflict"), "left\n").unwrap();
fs::write(root.join("unselected"), vec![b'x'; 17 * 1024 * 1024]).unwrap();
jj(&root, &["describe", "-m", "left"]);
let left = jj(&root, &["log", "--no-graph", "-r", "@", "-T", "commit_id"]);
jj(&root, &["new", &base]);
fs::write(root.join("conflict"), "right\n").unwrap();
jj(&root, &["describe", "-m", "right"]);
let right = jj(&root, &["log", "--no-graph", "-r", "@", "-T", "commit_id"]);
jj(&root, &["new", &left, &right, "-m", "conflicted merge"]);
let merged = jj(&root, &["log", "--no-graph", "-r", "@", "-T", "commit_id"]);
let state = disk_state(&root);
let mut repository = open(&root);
let comparison = repository
.compare(
1,
&revision(&base, RepoKind::Jujutsu),
&revision(&merged, RepoKind::Jujutsu),
ComparisonMode::BranchChanges,
&[],
)
.unwrap();
assert_eq!(
comparison
.files
.iter()
.map(|file| file.path.as_path())
.collect::<Vec<_>>(),
[Path::new("conflict"), Path::new("unselected")]
);
let conflict = repository.load_file(1, 0).unwrap();
assert_eq!(conflict.before, ["base"]);
let projection = jj(
&root,
&[
"--ignore-working-copy",
"file",
"show",
"-r",
&merged,
"-T",
"",
"--",
"conflict",
],
);
assert_eq!(conflict.after, projection.lines().collect::<Vec<_>>());
assert!(!conflict.hunks.is_empty());
assert!(repository.load_file(1, 1).is_err());
assert_eq!(disk_state(&root), state);
jj(&root, &["new", &merged]);
fs::write(root.join("extra"), "extra\n").unwrap();
jj(
&root,
&["describe", "-m", "unrelated change retaining conflict"],
);
let next = jj(&root, &["log", "--no-graph", "-r", "@", "-T", "commit_id"]);
let comparison = repository
.compare(
2,
&revision(&merged, RepoKind::Jujutsu),
&revision(&next, RepoKind::Jujutsu),
ComparisonMode::Snapshots,
&[],
)
.unwrap();
assert_eq!(
comparison
.files
.iter()
.map(|file| file.path.as_path())
.collect::<Vec<_>>(),
[Path::new("extra")]
);
}
#[test]
fn unsaved_overlays_are_working_after_only_and_cannot_follow_nonregular_paths() {
let fixture = Fixture::new();