feat: show jj ancestry in the floating diff picker

This commit is contained in:
Valdemar Lorenzen
2026-10-23 17:08:53 +02:00
parent 496c93774f
commit e3d44f24c0
16 changed files with 882 additions and 50 deletions
+176 -4
View File
@@ -394,6 +394,7 @@ fn revision(
fn catalog() -> Catalog {
Catalog {
kind: RepoKind::Git,
graph: Default::default(),
entries: vec![
revision(
RevisionTarget::Working,
@@ -1573,7 +1574,7 @@ fn revision_matches_rank_later_contiguous_boundaries_and_tight_subsequences() {
}
#[test]
fn jujutsu_fuzzy_selectors_rank_labels_above_descriptions_in_both_stages() {
fn jujutsu_fuzzy_selectors_preserve_catalog_order_in_both_stages() {
for phase in [Phase::Before, Phase::After] {
for (category_index, &category) in RepoKind::Jujutsu.categories().iter().enumerate() {
let mut state = DiffState::new();
@@ -1581,6 +1582,7 @@ fn jujutsu_fuzzy_selectors_rank_labels_above_descriptions_in_both_stages() {
1,
Ok(Outcome::Catalog(Catalog {
kind: RepoKind::Jujutsu,
graph: Default::default(),
entries: vec![
revision(
RevisionTarget::Working,
@@ -1621,15 +1623,185 @@ fn jujutsu_fuzzy_selectors_rank_labels_above_descriptions_in_both_stages() {
.map(|entry| &entry.target)
.collect::<Vec<_>>(),
[
&RevisionTarget::Commit("immutable-exact-target".into()),
&RevisionTarget::Commit("immutable-fuzzy-target".into()),
&RevisionTarget::Commit("description-target".into()),
&RevisionTarget::Commit("immutable-fuzzy-target".into()),
&RevisionTarget::Commit("immutable-exact-target".into()),
]
);
}
}
}
fn graph_state() -> DiffState {
let mut catalog = Catalog {
kind: RepoKind::Jujutsu,
graph: Default::default(),
notice: None,
entries: vec![revision(
RevisionTarget::Working,
"Working",
"",
Category::Revisions,
)],
};
for (id, parents) in [
("merge", vec!["left", "right"]),
("left", vec!["hidden"]),
("hidden", vec!["root"]),
("right", vec!["root"]),
("root", vec![]),
] {
catalog.entries.push(revision(
RevisionTarget::Commit(id.into()),
id,
"needle",
Category::Revisions,
));
catalog.graph.insert(
id.into(),
RevisionGraph {
parents: parents.into_iter().map(String::from).collect(),
..Default::default()
},
);
}
catalog.entries.push(revision(
RevisionTarget::Bookmark {
name: "topic".into(),
remote: None,
},
"topic",
"change merge needle",
Category::Bookmarks,
));
catalog.entries.push(revision(
RevisionTarget::Bookmark {
name: "base".into(),
remote: None,
},
"base",
"change root needle",
Category::Bookmarks,
));
let mut state = DiffState::new();
state.apply_result(1, Ok(Outcome::Catalog(catalog)));
state
}
#[test]
fn picker_graph_preserves_forks_merges_and_marks_filtered_ancestry() {
let mut state = graph_state();
let graph = state.picker_graph();
assert_eq!(graph.len(), 5);
assert_eq!(graph["merge"][1].trim_end(), "├─┐");
assert_eq!(graph["right"][1].trim_end(), "├─┘");
assert!(!graph.values().flatten().any(|prefix| prefix.contains('~')));
state.category = 1;
state.refilter();
assert_eq!(state.entries().len(), 3);
let graph = state.picker_graph();
assert_eq!(
graph.keys().map(String::as_str).collect::<Vec<_>>(),
["merge", "root"]
);
assert!(graph["merge"][1].contains('~'));
assert_eq!(
state
.graph_revision(state.entries().nth(1).unwrap())
.unwrap()
.0,
"merge"
);
assert!(matches!(
state.entries().nth(1).unwrap().target,
RevisionTarget::Bookmark { .. }
));
state.category = 0;
state.query = "left root".into();
state.refilter();
assert!(state.picker_graph().is_empty());
assert_eq!(state.entries().len(), 1);
state.before = Some(state.catalog.as_ref().unwrap().entries[1].clone());
state.query = "right".into();
state.refilter();
assert_eq!(state.picker_graph().len(), 2);
assert!(state.picker_graph()["merge"][1].contains('~'));
}
#[test]
fn graph_crossings_do_not_join_unrelated_ancestry() {
let mut state = DiffState::new();
let mut catalog = Catalog {
kind: RepoKind::Jujutsu,
entries: Vec::new(),
graph: Default::default(),
notice: None,
};
for (id, parents) in [
("a", vec!["x"]),
("b", vec!["y"]),
("merge", vec!["x", "z"]),
("x", vec![]),
("y", vec![]),
("z", vec![]),
] {
catalog.entries.push(revision(
RevisionTarget::Commit(id.into()),
id,
"",
Category::Revisions,
));
catalog.graph.insert(
id.into(),
RevisionGraph {
parents: parents.into_iter().map(String::from).collect(),
..Default::default()
},
);
}
state.apply_result(1, Ok(Outcome::Catalog(catalog)));
assert_eq!(state.picker_graph()["merge"][1].trim_end(), "├─╪─┤");
}
#[test]
fn before_identity_survives_query_and_category_without_duplicate_graph_nodes() {
let mut state = graph_state();
state.category = 1;
state.refilter();
state.selected = 1;
state.choose();
let before = state.before.clone().unwrap();
assert!(matches!(before.target, RevisionTarget::Bookmark { .. }));
state.category = 0;
state.refilter();
assert_eq!(
state
.entries()
.filter(|entry| state
.graph_revision(entry)
.is_some_and(|(id, _)| id == "merge"))
.count(),
1
);
for category in 0..2 {
state.category = category;
state.query = "unmatched".into();
state.refilter();
assert_eq!(state.entries().len(), 2);
let retained = state
.entries()
.find(|entry| entry.target == before.target)
.unwrap();
assert_eq!(retained.target, before.target);
assert_eq!(state.before.as_ref(), Some(&before));
state.selected = 1;
assert_eq!(
state.entries().nth(state.selected).unwrap().target,
before.target
);
}
}
#[test]
fn selector_filters_literal_unicode_tokens_and_pins_working_in_each_category() {
let mut editor = editor();
@@ -1746,7 +1918,7 @@ fn loader_preserves_after_step_query_category_and_selection_then_compares() {
state.entries().nth(state.selected).unwrap().target,
RevisionTarget::FetchRemote("origin".into())
);
assert_eq!(state.entries().len(), 3);
assert_eq!(state.entries().len(), 4);
let remote_position = state
.entries()
.position(