diff --git a/CHANGELOG.md b/CHANGELOG.md index fb01d03..86d7803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Git remotes in the status bar, with push & fetch commands. - "Create branch" command on revisions. +- Display edges to commits that aren't in the queried revset, by drawing a line to nowhere. - New config option gg.queries.log-page-size. - Miscellaneous design improvements. diff --git a/TODO.md b/TODO.md index 0bc9437..0a8bc62 100644 --- a/TODO.md +++ b/TODO.md @@ -12,6 +12,7 @@ These changes may or may not be implemented in the future. * perf: better solution to slow immutability check - jj-lib will have a revset contains cache soon * feat: alternate drag modes for copy/duplicate, maybe for rebase-all-descendants * feat: log multiselect +* feat: log enter key * feat: log filters (find commits that change path etc) * feat: file select/multiselect? large moves could be tedious otherwise. maybe file menu? * feat: redo/undo stack @@ -19,10 +20,12 @@ These changes may or may not be implemented in the future. * feat: sub-file hunk changes * feat: diffs and/or difftool * feat: resolve workflow -* feat: tags display & management +* feat: tags display (readonly, perhaps - look at jj support) * feat: view commit ids in log (configurable?) * feat: view repo at different ops (slider? entire pane?) * feat: progress display (probably in statusbar); useful for git & snapshot +* feat: repo in title +* feat: finish branch management * feat: structured op descs - want to be able to present them more nicely, extracting ids etc. tags? - there's a request for this to be part of jj * feat: more mutations @@ -33,7 +36,6 @@ These changes may or may not be implemented in the future. * feat: more settings - log revsets * design: decide whether to remove edit menu and maybe add others -* design: draw missing (edge-to-nowhere) graph nodes? * design: consider common signature control * epic: categorical expansion - trays, modals, pinned commits etc * chore: windows codesigning will break in august 2024; needs a new approach \ No newline at end of file diff --git a/src-tauri/src/messages/queries.rs b/src-tauri/src/messages/queries.rs index 3d01548..25a0a36 100644 --- a/src-tauri/src/messages/queries.rs +++ b/src-tauri/src/messages/queries.rs @@ -185,6 +185,11 @@ pub enum LogLine { target: LogCoordinates, indirect: bool, }, + ToMissing { + source: LogCoordinates, + target: LogCoordinates, + indirect: bool, + }, } #[derive(Serialize, Debug)] diff --git a/src-tauri/src/worker/queries.rs b/src-tauri/src/worker/queries.rs index a2bf130..9cf28bd 100644 --- a/src-tauri/src/worker/queries.rs +++ b/src-tauri/src/worker/queries.rs @@ -7,6 +7,7 @@ use jj_lib::{ backend::{BackendError, CommitId}, matchers::EverythingMatcher, merged_tree::TreeDiffStream, + repo::Repo, revset::Revset, revset_graph::{RevsetGraphEdge, RevsetGraphEdgeType, TopoGroupedRevsetGraphIterator}, rewrite, @@ -78,6 +79,7 @@ impl<'a, 'b> QuerySession<'a, 'b> { let mut rows: Vec = Vec::with_capacity(self.state.page_size); // output rows to draw let mut row = self.state.next_row; let max = row + self.state.page_size; + let root_id = self.ws.repo().store().root_commit_id().clone(); while let Some((commit_id, commit_edges)) = self.iter.next() { // output lines to draw for the current row @@ -88,14 +90,9 @@ impl<'a, 'b> QuerySession<'a, 'b> { let mut stem_known_immutable = false; let mut padding = 0; // used to offset the commit summary past some edges - for (slot, stem) in self.state.stems.iter().enumerate() { - if let Some(LogStem { target, .. }) = stem { - if *target == commit_id { - column = slot; - padding = self.state.stems.len() - column - 1; - break; - } - } + if let Some(slot) = self.find_stem_for_commit(&commit_id) { + column = slot; + padding = self.state.stems.len() - column - 1; } // terminate any existing stem, removing it from the end or leaving a gap @@ -154,16 +151,23 @@ impl<'a, 'b> QuerySession<'a, 'b> { .truncate(self.state.stems.len() - empty_stems); // merge edges into existing stems or add new ones to the right + let mut next_missing: Option = None; 'edges: for edge in commit_edges.iter() { if edge.edge_type == RevsetGraphEdgeType::Missing { - continue; + if edge.target == root_id { + continue; + } else { + next_missing = Some(edge.target.clone()); + } } + let indirect = edge.edge_type != RevsetGraphEdgeType::Direct; + for (slot, stem) in self.state.stems.iter().enumerate() { if let Some(stem) = stem { if stem.target == edge.target { lines.push(LogLine::ToIntersection { - indirect: edge.edge_type == RevsetGraphEdgeType::Indirect, + indirect, source: LogCoordinates(column, row), target: LogCoordinates(slot, row + 1), }); @@ -177,7 +181,7 @@ impl<'a, 'b> QuerySession<'a, 'b> { *stem = Some(LogStem { source: LogCoordinates(column, row), target: edge.target.clone(), - indirect: edge.edge_type == RevsetGraphEdgeType::Indirect, + indirect, was_inserted: true, known_immutable: header.is_immutable, }); @@ -188,7 +192,7 @@ impl<'a, 'b> QuerySession<'a, 'b> { self.state.stems.push(Some(LogStem { source: LogCoordinates(column, row), target: edge.target.clone(), - indirect: edge.edge_type == RevsetGraphEdgeType::Indirect, + indirect, was_inserted: false, known_immutable: header.is_immutable, })); @@ -200,8 +204,27 @@ impl<'a, 'b> QuerySession<'a, 'b> { padding, lines, }); - row = row + 1; + + // terminate any temporary stems created for missing edges + match next_missing + .take() + .and_then(|id| self.find_stem_for_commit(&id)) + { + Some(slot) => { + if let Some(terminated_stem) = &self.state.stems[slot] { + rows.last_mut().unwrap().lines.push(LogLine::ToMissing { + indirect: terminated_stem.indirect, + source: LogCoordinates(column, row - 1), + target: LogCoordinates(slot, row), + }); + } + self.state.stems[slot] = None; + row = row + 1; + } + None => (), + }; + if row == max { break; } @@ -213,6 +236,18 @@ impl<'a, 'b> QuerySession<'a, 'b> { has_more: self.iter.peek().is_some(), }) } + + fn find_stem_for_commit(&self, id: &CommitId) -> Option { + for (slot, stem) in self.state.stems.iter().enumerate() { + if let Some(LogStem { target, .. }) = stem { + if target == id { + return Some(slot); + } + } + } + + None + } } // XXX this is reloading the header, which the client already has diff --git a/src/LogPane.svelte b/src/LogPane.svelte index cd3cd2a..3559df2 100644 --- a/src/LogPane.svelte +++ b/src/LogPane.svelte @@ -130,7 +130,7 @@ let enhancedLine = line as EnhancedLine; enhancedLine.key = lineKey++; - if (line.type == "ToIntersection") { + if (line.type == "ToIntersection" || line.type == "ToMissing") { // ToIntersection lines begin at their owning row, so they run from this row to the next one that we read (which may not be on the same page) enhancedLine.child = row.revision; enhancedRow.passingLines.push(enhancedLine); diff --git a/src/messages/LogLine.ts b/src/messages/LogLine.ts index cce7cde..13242cb 100644 --- a/src/messages/LogLine.ts +++ b/src/messages/LogLine.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { LogCoordinates } from "./LogCoordinates"; -export type LogLine = { "type": "FromNode", source: LogCoordinates, target: LogCoordinates, indirect: boolean, } | { "type": "ToNode", source: LogCoordinates, target: LogCoordinates, indirect: boolean, } | { "type": "ToIntersection", source: LogCoordinates, target: LogCoordinates, indirect: boolean, }; \ No newline at end of file +export type LogLine = { "type": "FromNode", source: LogCoordinates, target: LogCoordinates, indirect: boolean, } | { "type": "ToNode", source: LogCoordinates, target: LogCoordinates, indirect: boolean, } | { "type": "ToIntersection", source: LogCoordinates, target: LogCoordinates, indirect: boolean, } | { "type": "ToMissing", source: LogCoordinates, target: LogCoordinates, indirect: boolean, }; \ No newline at end of file