fix: simplify revision diff selection into a flat list
Revisions still used ancestry lanes and placed author/date/bookmark metadata before the summary. Show short change IDs and summaries first, move metadata to a dimmed detail row, and remove the obsolete picker graph projection.
This commit is contained in:
+3
-3
@@ -390,11 +390,11 @@ The project root is the working directory captured at startup. Scanning uses the
|
|||||||
|
|
||||||
Run `:diff` in Normal mode, choose **Before**, then **After**, and press Enter to compare. The command palette also exposes **Diff**; there is no default keybinding. Repository discovery starts at the active file's directory, or the startup project root for an unnamed buffer, and prefers jj over Git.
|
Run `:diff` in Normal mode, choose **Before**, then **After**, and press Enter to compare. The command palette also exposes **Diff**; there is no default keybinding. Repository discovery starts at the active file's directory, or the startup project root for an unnamed buffer, and prefers jj over Git.
|
||||||
|
|
||||||
The floating selector has **Revisions / Bookmarks** tabs for jj, or **Commits / Branches / Tags** for Git. jj **Revisions** uses a log graph: `@` marks the recorded checkout revision, `◆` immutable revisions, and `○` other revisions; `~` marks skipped ancestry. Each revision shows its short change ID, author, date, bookmarks, commit ID, and description. **Bookmarks** is a flat, name-first list with the commit description underneath, without graph lanes or revision metadata crowding out names. Local and remote-tracking bookmarks remain individually selectable, including multiple names on one revision, preserving their symbolic follow behavior. Narrow windows use compact single-line entries.
|
The floating selector has **Revisions / Bookmarks** tabs for jj, or **Commits / Branches / Tags** for Git. Both jj tabs use flat lists without ancestry lanes. **Revisions** puts the short change ID and commit description on the first line; bookmarks, author, date, commit ID, and textual **[checkout]** / **[immutable]** badges appear on a dimmed second line. **Bookmarks** puts the name first, with the commit description underneath. Local and remote-tracking bookmarks remain individually selectable, including multiple names on one revision, preserving their symbolic follow behavior. Narrow windows omit revision metadata and use compact single-line entries.
|
||||||
|
|
||||||
Both stages support case-insensitive fuzzy search by revision/change ID, commit ID, reference name, or description: `f1205pas` can find `sm/fin-1205-pilot-assignment-schema`. Space-separated tokens must all match, but can match different fields; a token cannot span unrelated fields. jj keeps graph order while filtering. Git ranks exact, prefix, and contiguous matches ahead of scattered subsequences, with names/IDs ahead of descriptions and catalogue order breaking ties. An empty query restores catalogue order. After choosing Before, its revision carries a **[Before]** badge instead of a separate summary row. It remains included through search and category changes, but scrolls normally.
|
Both stages support case-insensitive fuzzy search by revision/change ID, commit ID, reference name, or description: `f1205pas` can find `sm/fin-1205-pilot-assignment-schema`. Space-separated tokens must all match, but can match different fields; a token cannot span unrelated fields. jj keeps catalogue order while filtering. Git ranks exact, prefix, and contiguous matches ahead of scattered subsequences, with names/IDs ahead of descriptions and catalogue order breaking ties. An empty query restores catalogue order. After choosing Before, its revision carries a **[Before]** badge instead of a separate summary row. It remains included through search and category changes, but scrolls normally.
|
||||||
|
|
||||||
The separate checkout choice stays first in every category regardless of the query. It is **Checkout snapshot** during Before selection: a frozen disk snapshot including eligible untracked files. During After selection it is **Follow checkout** when `config.diff_follow_working_copy` is enabled, otherwise **Checkout snapshot**. Either After choice includes unsaved regular-text buffers and enables live editing; opening a comparison never saves them. Unlike this live-files choice, selecting `@` in the graph pins the recorded revision.
|
The separate checkout choice stays first in every category regardless of the query. It is **Checkout snapshot** during Before selection: a frozen disk snapshot including eligible untracked files. During After selection it is **Follow checkout** when `config.diff_follow_working_copy` is enabled, otherwise **Checkout snapshot**. Either After choice includes unsaved regular-text buffers and enables live editing; opening a comparison never saves them. Unlike this live-files choice, selecting the recorded checkout revision in Revisions pins that revision.
|
||||||
|
|
||||||
New comparisons default to **Branch changes**: Before becomes the unique merge base of the selected Before and After revisions, and After stays the selected tip. Changes exclusive to the Before branch are excluded. The Before border identifies both selected names and the immutable merge-base commit ID. **Snapshots** compares the two selected snapshots directly. **Ctrl+b** toggles the mode, also available as **`:diff.mode.toggle`**, the palette action, and remappable **`config.core.diff.mode.toggle`**. Toggling an opened comparison recomputes using the original selected endpoints. Selecting **Checkout snapshot** or **Follow checkout** immediately forces **Snapshots**; Branch changes is unavailable while either is selected. Missing or multiple merge bases produce an explicit, recoverable selector error rather than silently falling back; choose Snapshots to compare such histories. jj's synthetic root is the empty ancestor of every jj history.
|
New comparisons default to **Branch changes**: Before becomes the unique merge base of the selected Before and After revisions, and After stays the selected tip. Changes exclusive to the Before branch are excluded. The Before border identifies both selected names and the immutable merge-base commit ID. **Snapshots** compares the two selected snapshots directly. **Ctrl+b** toggles the mode, also available as **`:diff.mode.toggle`**, the palette action, and remappable **`config.core.diff.mode.toggle`**. Toggling an opened comparison recomputes using the original selected endpoints. Selecting **Checkout snapshot** or **Follow checkout** immediately forces **Snapshots**; Branch changes is unavailable while either is selected. Missing or multiple merge bases produce an explicit, recoverable selector error rather than silently falling back; choose Snapshots to compare such histories. jj's synthetic root is the empty ancestor of every jj history.
|
||||||
|
|
||||||
|
|||||||
-172
@@ -821,7 +821,6 @@ pub struct DiffState {
|
|||||||
pending: Option<Operation>,
|
pending: Option<Operation>,
|
||||||
request_kind: Option<RequestKind>,
|
request_kind: Option<RequestKind>,
|
||||||
visible: Vec<usize>,
|
visible: Vec<usize>,
|
||||||
picker_graph: BTreeMap<String, [String; 2]>,
|
|
||||||
bookmark_graph: BTreeMap<String, BTreeMap<Option<String>, String>>,
|
bookmark_graph: BTreeMap<String, BTreeMap<Option<String>, String>>,
|
||||||
search_fields: Vec<[String; 3]>,
|
search_fields: Vec<[String; 3]>,
|
||||||
search_matches: Vec<([usize; 7], usize)>,
|
search_matches: Vec<([usize; 7], usize)>,
|
||||||
@@ -891,7 +890,6 @@ impl DiffState {
|
|||||||
pending: Some(Operation::Discover),
|
pending: Some(Operation::Discover),
|
||||||
request_kind: Some(RequestKind::Catalog),
|
request_kind: Some(RequestKind::Catalog),
|
||||||
visible: Vec::new(),
|
visible: Vec::new(),
|
||||||
picker_graph: BTreeMap::new(),
|
|
||||||
bookmark_graph: BTreeMap::new(),
|
bookmark_graph: BTreeMap::new(),
|
||||||
search_fields: Vec::new(),
|
search_fields: Vec::new(),
|
||||||
search_matches: Vec::new(),
|
search_matches: Vec::new(),
|
||||||
@@ -1600,175 +1598,6 @@ impl DiffState {
|
|||||||
.map(|(id, graph)| (id.as_str(), graph))
|
.map(|(id, graph)| (id.as_str(), graph))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn picker_graph(&self) -> &BTreeMap<String, [String; 2]> {
|
|
||||||
&self.picker_graph
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rebuild_picker_graph(&mut self) {
|
|
||||||
if self.categories().get(self.category) != Some(&Category::Revisions) {
|
|
||||||
self.picker_graph.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut result = BTreeMap::new();
|
|
||||||
let Some(catalog) = &self.catalog else {
|
|
||||||
self.picker_graph = result;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let visible: Vec<_> = self
|
|
||||||
.entries()
|
|
||||||
.filter_map(|entry| self.graph_revision(entry))
|
|
||||||
.map(|(id, _)| id)
|
|
||||||
.collect();
|
|
||||||
let selected: BTreeSet<_> = visible.iter().copied().collect();
|
|
||||||
if selected.is_empty() {
|
|
||||||
self.picker_graph = result;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut nearest: BTreeMap<&str, BTreeMap<&str, bool>> = BTreeMap::new();
|
|
||||||
for entry in catalog
|
|
||||||
.entries
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.filter(|entry| entry.category == Category::Revisions)
|
|
||||||
{
|
|
||||||
let Some((id, graph)) = self.graph_revision(entry) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if nearest.contains_key(id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut ancestors = BTreeMap::new();
|
|
||||||
if selected.contains(id) {
|
|
||||||
ancestors.insert(id, false);
|
|
||||||
} else {
|
|
||||||
for parent in &graph.parents {
|
|
||||||
if let Some(parents) = nearest.get(parent.as_str()) {
|
|
||||||
ancestors.extend(parents.keys().map(|&parent| (parent, true)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nearest.insert(id, ancestors);
|
|
||||||
}
|
|
||||||
let mut lanes: Vec<(&str, bool)> = Vec::new();
|
|
||||||
for id in visible {
|
|
||||||
if result.contains_key(id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let graph = &catalog.graph[id];
|
|
||||||
let mut parents = BTreeMap::new();
|
|
||||||
for parent in &graph.parents {
|
|
||||||
if let Some(ancestors) = nearest.get(parent.as_str()) {
|
|
||||||
for (&ancestor, &skipped) in ancestors {
|
|
||||||
parents
|
|
||||||
.entry(ancestor)
|
|
||||||
.and_modify(|old| *old |= skipped)
|
|
||||||
.or_insert(skipped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let column = lanes
|
|
||||||
.iter()
|
|
||||||
.position(|&(lane, _)| lane == id)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
lanes.push((id, false));
|
|
||||||
lanes.len() - 1
|
|
||||||
});
|
|
||||||
let mut next = lanes.clone();
|
|
||||||
next.remove(column);
|
|
||||||
for (offset, (&parent, &skipped)) in parents.iter().enumerate() {
|
|
||||||
if let Some(lane) = next.iter_mut().find(|(lane, _)| *lane == parent) {
|
|
||||||
lane.1 |= skipped;
|
|
||||||
} else {
|
|
||||||
next.insert((column + offset).min(next.len()), (parent, skipped));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let width = lanes.len().max(next.len()) * 2;
|
|
||||||
let mut first = vec![' '; width];
|
|
||||||
for (column, &(_, skipped)) in lanes.iter().enumerate() {
|
|
||||||
first[column * 2] = if skipped { '~' } else { '│' };
|
|
||||||
}
|
|
||||||
first[column * 2] = if graph.current {
|
|
||||||
'@'
|
|
||||||
} else if graph.immutable {
|
|
||||||
'◆'
|
|
||||||
} else {
|
|
||||||
'○'
|
|
||||||
};
|
|
||||||
// Low bits connect up/down/left/right; high bits distinguish joins from crossings.
|
|
||||||
let mut links = vec![0u8; width];
|
|
||||||
for (from, &(lane, _)) in lanes.iter().enumerate() {
|
|
||||||
for (to, &(target, _)) in next.iter().enumerate() {
|
|
||||||
if (lane == id && !parents.contains_key(target))
|
|
||||||
|| (lane != id && lane != target)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let (from, to) = (from * 2, to * 2);
|
|
||||||
if links[from] & 1 != 0 {
|
|
||||||
links[from] |= 16;
|
|
||||||
}
|
|
||||||
if links[to] & 2 != 0 {
|
|
||||||
links[to] |= 32;
|
|
||||||
}
|
|
||||||
if from == to {
|
|
||||||
links[from] |= 3;
|
|
||||||
} else {
|
|
||||||
links[from] |= 1 | if from < to { 8 } else { 4 };
|
|
||||||
links[to] |= 2 | if from < to { 4 } else { 8 };
|
|
||||||
for cell in &mut links[from.min(to) + 1..from.max(to)] {
|
|
||||||
*cell |= 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut second: Vec<_> = links
|
|
||||||
.into_iter()
|
|
||||||
.map(|link| match link & 15 {
|
|
||||||
3 => '│',
|
|
||||||
5 => '┘',
|
|
||||||
6 => '┐',
|
|
||||||
7 => '┤',
|
|
||||||
9 => '└',
|
|
||||||
10 => '┌',
|
|
||||||
11 => '├',
|
|
||||||
12 => '─',
|
|
||||||
13 => '┴',
|
|
||||||
14 => '┬',
|
|
||||||
15 => {
|
|
||||||
if link & 48 == 0 {
|
|
||||||
'╪'
|
|
||||||
} else {
|
|
||||||
'┼'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => ' ',
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
for (column, &(_, skipped)) in next.iter().enumerate() {
|
|
||||||
if skipped {
|
|
||||||
second[column * 2] = '~';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !graph.parents.is_empty() && parents.is_empty() {
|
|
||||||
second[column * 2] = '~';
|
|
||||||
}
|
|
||||||
if !parents.is_empty()
|
|
||||||
&& graph
|
|
||||||
.parents
|
|
||||||
.iter()
|
|
||||||
.any(|parent| nearest.get(parent.as_str()).is_none_or(BTreeMap::is_empty))
|
|
||||||
{
|
|
||||||
second.extend(['~', ' ']);
|
|
||||||
}
|
|
||||||
result.insert(
|
|
||||||
id.to_owned(),
|
|
||||||
[first.into_iter().collect(), second.into_iter().collect()],
|
|
||||||
);
|
|
||||||
lanes = next;
|
|
||||||
}
|
|
||||||
self.picker_graph = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refilter(&mut self) {
|
fn refilter(&mut self) {
|
||||||
self.visible.clear();
|
self.visible.clear();
|
||||||
self.search_matches.clear();
|
self.search_matches.clear();
|
||||||
@@ -1907,7 +1736,6 @@ impl DiffState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.selected = 0;
|
self.selected = 0;
|
||||||
self.rebuild_picker_graph();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_target(&mut self, target: &RevisionTarget) {
|
fn select_target(&mut self, target: &RevisionTarget) {
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ Restart: :restart closes clean buffers; unsaved buffers remain open and block re
|
|||||||
Tabs: :tab.new, :tab.previous, :tab.next, :tab.close, :tab.force_close
|
Tabs: :tab.new, :tab.previous, :tab.next, :tab.close, :tab.force_close
|
||||||
Tabs retain pane layouts and independent views; close protects exclusively displayed unsaved edits
|
Tabs retain pane layouts and independent views; close protects exclusively displayed unsaved edits
|
||||||
Markdown links: :follow_link (primary caret; local files only)
|
Markdown links: :follow_link (primary caret; local files only)
|
||||||
Diff: :diff (Normal); floating jj revision graph / bookmark list; choose Before then After; Before marked inline
|
Diff: :diff (Normal); floating revision/bookmark lists; choose Before then After; Before marked inline
|
||||||
Before and historical/remote After stay read-only; Tab/Shift+Tab categories/Normal focus
|
Before and historical/remote After stay read-only; Tab/Shift+Tab categories/Normal focus
|
||||||
All text panes use native motions, selections and copy; read-only panes reject edits/saves
|
All text panes use native motions, selections and copy; read-only panes reject edits/saves
|
||||||
Switching Before/After preserves the aligned cursor row and display column
|
Switching Before/After preserves the aligned cursor row and display column
|
||||||
|
|||||||
+23
-41
@@ -3880,7 +3880,6 @@ fn draw_diff(
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
let decoration_width = symbol_width + usize::from(symbol_width > 0);
|
let decoration_width = symbol_width + usize::from(symbol_width > 0);
|
||||||
let graph = diff.picker_graph();
|
|
||||||
for (index, revision) in entries
|
for (index, revision) in entries
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip(first)
|
.skip(first)
|
||||||
@@ -3899,58 +3898,41 @@ fn draw_diff(
|
|||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
if let Some((id, metadata)) = diff.graph_revision(revision)
|
if revision.category == crate::diff::Category::Revisions
|
||||||
&& let Some(prefix) = graph.get(id)
|
&& let Some((id, metadata)) = diff.graph_revision(revision)
|
||||||
{
|
{
|
||||||
let width = usize::from(results.width);
|
let width = usize::from(results.width);
|
||||||
let change = metadata.change_id.as_str();
|
let change = metadata.change_id.as_str();
|
||||||
let change = change.get(..12).unwrap_or(change);
|
let change = change.get(..12).unwrap_or(change);
|
||||||
let label = format!(
|
let label = format!(
|
||||||
"{} {}{}{} {} {} {} {}",
|
"{}{before_badge} {} · {}",
|
||||||
if index == diff.selected { ">" } else { " " },
|
if index == diff.selected { ">" } else { " " },
|
||||||
prefix[0],
|
|
||||||
display_text(change),
|
display_text(change),
|
||||||
before_badge,
|
display_text(&metadata.description),
|
||||||
display_text(&metadata.author),
|
|
||||||
display_text(&metadata.timestamp),
|
|
||||||
if metadata.bookmarks.is_empty() {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
format!("[{}]", display_text(&metadata.bookmarks))
|
|
||||||
},
|
|
||||||
&id[..id.len().min(12)],
|
|
||||||
);
|
);
|
||||||
|
lines.push(Line::styled(visible_line(&label, 0, width), style));
|
||||||
if row_height == 2 {
|
if row_height == 2 {
|
||||||
lines.push(Line::styled(visible_line(&label, 0, width), style));
|
let details = format!(
|
||||||
|
" {}{}{}{} · {} · {}",
|
||||||
|
if metadata.current { "[checkout] " } else { "" },
|
||||||
|
if metadata.immutable {
|
||||||
|
"[immutable] "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
if metadata.bookmarks.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!("[{}] ", display_text(&metadata.bookmarks))
|
||||||
|
},
|
||||||
|
display_text(&metadata.author),
|
||||||
|
display_text(&metadata.timestamp),
|
||||||
|
&id[..id.len().min(12)],
|
||||||
|
);
|
||||||
lines.push(Line::styled(
|
lines.push(Line::styled(
|
||||||
visible_line(
|
visible_line(&details, 0, width),
|
||||||
&format!(" {}{}", prefix[1], display_text(&metadata.description)),
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
),
|
|
||||||
style.fg(Color::DarkGray),
|
style.fg(Color::DarkGray),
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
lines.push(Line::styled(
|
|
||||||
visible_line(
|
|
||||||
&format!(
|
|
||||||
"{} {}{}{}{} · {}",
|
|
||||||
if index == diff.selected { ">" } else { " " },
|
|
||||||
prefix[0],
|
|
||||||
if prefix[1].contains('~') { "~ " } else { "" },
|
|
||||||
if before_badge.is_empty() {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
"[Before] "
|
|
||||||
},
|
|
||||||
display_text(change),
|
|
||||||
display_text(&metadata.description),
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
),
|
|
||||||
style,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1688,75 +1688,6 @@ fn graph_state() -> DiffState {
|
|||||||
state
|
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);
|
|
||||||
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]
|
#[test]
|
||||||
fn before_identity_survives_query_and_category_without_duplicate_graph_nodes() {
|
fn before_identity_survives_query_and_category_without_duplicate_graph_nodes() {
|
||||||
let mut state = graph_state();
|
let mut state = graph_state();
|
||||||
|
|||||||
+13
-7
@@ -3561,7 +3561,7 @@ fn checkout_picker_labels_follow_the_configured_stage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn jj_picker_renders_graph_before_badge_and_compacts_without_losing_controls() {
|
fn jj_revision_picker_keeps_summaries_and_before_visible_without_graph_lanes() {
|
||||||
use crate::diff::{Category, RepoKind, Revision, RevisionGraph, RevisionTarget};
|
use crate::diff::{Category, RepoKind, Revision, RevisionGraph, RevisionTarget};
|
||||||
let mut catalog = diff_catalog();
|
let mut catalog = diff_catalog();
|
||||||
catalog.kind = RepoKind::Jujutsu;
|
catalog.kind = RepoKind::Jujutsu;
|
||||||
@@ -3582,10 +3582,10 @@ fn jj_picker_renders_graph_before_badge_and_compacts_without_losing_controls() {
|
|||||||
change_id: change.into(),
|
change_id: change.into(),
|
||||||
description: format!("{change} description"),
|
description: format!("{change} description"),
|
||||||
parents: parent.into_iter().map(str::to_owned).collect(),
|
parents: parent.into_iter().map(str::to_owned).collect(),
|
||||||
author: "alice".into(),
|
author: "An author whose name must not displace the summary".into(),
|
||||||
timestamp: "2026-10-23".into(),
|
timestamp: "2026-10-23".into(),
|
||||||
bookmarks: if current {
|
bookmarks: if current {
|
||||||
"main".into()
|
"vl/fin-1441-1-ora-edi-recovery@origin".into()
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
},
|
},
|
||||||
@@ -3606,10 +3606,16 @@ fn jj_picker_renders_graph_before_badge_and_compacts_without_losing_controls() {
|
|||||||
render_diff(&mut terminal, &diff, &mut panels);
|
render_diff(&mut terminal, &diff, &mut panels);
|
||||||
let text = screen_text(&terminal);
|
let text = screen_text(&terminal);
|
||||||
assert!(text.contains("Choose After"));
|
assert!(text.contains("Choose After"));
|
||||||
assert!(text.contains("@"));
|
let summary = text
|
||||||
assert!(text.contains("change-a [Before] alice 2026-10-23 [main] aaaa1111"));
|
.lines()
|
||||||
assert!(text.contains("change-a description"));
|
.find(|line| line.contains("change-a description"))
|
||||||
assert!(!text.contains("Before:"));
|
.unwrap();
|
||||||
|
assert!(summary.contains("[Before]"), "{text}");
|
||||||
|
assert!(!summary.contains("An author"), "{text}");
|
||||||
|
assert!(
|
||||||
|
!text.contains('~') && !text.contains('○') && !text.contains('◆'),
|
||||||
|
"{text}"
|
||||||
|
);
|
||||||
let buffer = terminal.backend().buffer();
|
let buffer = terminal.backend().buffer();
|
||||||
let selected_rows: Vec<_> = (0..buffer.area.height)
|
let selected_rows: Vec<_> = (0..buffer.area.height)
|
||||||
.filter(|&y| (0..buffer.area.width).any(|x| buffer[(x, y)].bg == Color::Blue))
|
.filter(|&y| (0..buffer.area.width).any(|x| buffer[(x, y)].bg == Color::Blue))
|
||||||
|
|||||||
Reference in New Issue
Block a user