diff --git a/CHANGELOG.md b/CHANGELOG.md index 6498ec0c82e..64e1fe807df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). below it, similar to a "scissor line" in git. When editing multiple commits, only ignore until the next `JJ: describe` line. +* `jj evolog` now accepts `--reversed`. + ### Fixed bugs * The `$NO_COLOR` environment variable must now be non-empty to be respected. diff --git a/cli/src/commands/evolog.rs b/cli/src/commands/evolog.rs index 985a566ab2a..2647207357c 100644 --- a/cli/src/commands/evolog.rs +++ b/cli/src/commands/evolog.rs @@ -14,8 +14,12 @@ use clap_complete::ArgValueCandidates; use itertools::Itertools; +use jj_lib::backend::CommitId; use jj_lib::commit::Commit; use jj_lib::dag_walk::topo_order_reverse_ok; +use jj_lib::graph::reverse_graph; +use jj_lib::graph::GraphEdge; +use jj_lib::graph::GraphNode; use jj_lib::matchers::EverythingMatcher; use tracing::instrument; @@ -28,6 +32,7 @@ use crate::command_error::CommandError; use crate::commit_templater::CommitTemplateLanguage; use crate::complete; use crate::diff_util::DiffFormatArgs; +use crate::graphlog::convert_edge_type; use crate::graphlog::get_graphlog; use crate::graphlog::Edge; use crate::graphlog::GraphStyle; @@ -57,6 +62,9 @@ pub(crate) struct EvologArgs { value_name = "LIMIT" )] deprecated_limit: Option, + /// Show revisions in the opposite order (older revisions first) + #[arg(long)] + reversed: bool, /// Don't show the graph, show a flat list of revisions #[arg(long)] no_graph: bool, @@ -151,14 +159,36 @@ pub(crate) fn cmd_evolog( if !args.no_graph { let mut raw_output = formatter.raw()?; let mut graph = get_graphlog(graph_style, raw_output.as_mut()); - for commit in commits { - let edges = commit - .predecessor_ids() + + let commit_dag: Vec, ()>> = commits + .iter() + .map(|c| { + let edges = c + .predecessors() + .map(|p| GraphEdge::direct(p.unwrap().clone())) + .collect_vec(); + Ok((c.clone(), edges)) + }) + .collect_vec(); + + let iter_nodes: Box> = if args.reversed { + let reversed = reverse_graph(commit_dag.iter().cloned()).unwrap(); + Box::new(reversed.into_iter().map(Ok)) + } else { + Box::new(commit_dag.into_iter()) + }; + + for node in iter_nodes { + let (commit, edges) = node.unwrap(); + // Slightly different edge type used for display. + let graphlog_edges: Vec> = edges .iter() - .map(|id| Edge::Direct(id.clone())) - .collect_vec(); + .cloned() + .map(|e| convert_edge_type(e).map(|e| e.id().clone())) + .collect(); let mut buffer = vec![]; - let within_graph = with_content_format.sub_width(graph.width(commit.id(), &edges)); + let within_graph = + with_content_format.sub_width(graph.width(commit.id(), &graphlog_edges)); within_graph.write(ui.new_formatter(&mut buffer).as_mut(), |formatter| { template.format(&commit, formatter) })?; @@ -180,15 +210,20 @@ pub(crate) fn cmd_evolog( let node_symbol = format_template(ui, &Some(commit.clone()), &node_template); graph.add_node( commit.id(), - &edges, + &graphlog_edges, &node_symbol, &String::from_utf8_lossy(&buffer), )?; } } else { - for commit in commits { - with_content_format - .write(formatter, |formatter| template.format(&commit, formatter))?; + let commits_iter: Box> = if args.reversed { + Box::new(commits.iter().rev()) + } else { + Box::new(commits.iter()) + }; + + for commit in commits_iter { + with_content_format.write(formatter, |formatter| template.format(commit, formatter))?; if let Some(renderer) = &diff_renderer { let predecessors: Vec<_> = commit.predecessors().try_collect()?; let width = ui.term_width(); @@ -196,7 +231,7 @@ pub(crate) fn cmd_evolog( ui, formatter, &predecessors, - &commit, + commit, &EverythingMatcher, width, )?; diff --git a/cli/src/commands/log.rs b/cli/src/commands/log.rs index 8492e4fa9a8..4178dc824a7 100644 --- a/cli/src/commands/log.rs +++ b/cli/src/commands/log.rs @@ -208,7 +208,8 @@ pub(crate) fn cmd_log( } } if args.reversed { - Box::new(reverse_graph(forward_iter)?.into_iter().map(Ok)) + let reversed = reverse_graph(forward_iter)?; + Box::new(reversed.into_iter().map(Ok)) } else { Box::new(forward_iter) } diff --git a/cli/src/graphlog.rs b/cli/src/graphlog.rs index de168e43dc3..bdfc6366817 100644 --- a/cli/src/graphlog.rs +++ b/cli/src/graphlog.rs @@ -18,6 +18,8 @@ use std::io::Write; use itertools::Itertools; use jj_lib::config::ConfigGetError; +use jj_lib::graph::GraphEdge; +use jj_lib::graph::GraphEdgeType; use jj_lib::settings::UserSettings; use renderdag::Ancestor; use renderdag::GraphRowRenderer; @@ -31,6 +33,28 @@ pub enum Edge { Missing, } +impl Edge { + pub fn map(self, f: impl FnOnce(T) -> U) -> Edge { + match self { + Edge::Direct(inner) => Edge::Direct(f(inner)), + Edge::Indirect(inner) => Edge::Indirect(f(inner)), + Edge::Missing => Edge::Missing, + } + } +} + +/// Used to convert a jj_lib GraphEdge into a graphlog Edge. +pub fn convert_edge_type(e: GraphEdge) -> Edge { + // TODO: Is it possible for the ClI to use the GraphEdge type directly? + // These two types seem to correspond exactly, only differing in minor + // implementation details. + match e.edge_type { + GraphEdgeType::Missing => Edge::Missing, + GraphEdgeType::Direct => Edge::Direct(e.target), + GraphEdgeType::Indirect => Edge::Indirect(e.target), + } +} + pub trait GraphLog { fn add_node( &mut self, diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index ea7cbd178c3..9f728951061 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -798,6 +798,7 @@ Lists the previous commits which a change has pointed to. The current commit of Default value: `@` * `-n`, `--limit ` — Limit number of revisions to show +* `--reversed` — Show revisions in the opposite order (older revisions first) * `--no-graph` — Don't show the graph, show a flat list of revisions * `-T`, `--template