Skip to content

Commit

Permalink
Make node symbols templatable in the graphs.
Browse files Browse the repository at this point in the history
Adds config options
* templates.log_graph_node_commit
* templates.log_graph_node_op
* templates.log_graph_node_elided
  • Loading branch information
algmyr committed Mar 20, 2024
1 parent f865c1b commit 527bd2f
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 55 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `ui.default-command` now accepts multiple string arguments, for more complex
default `jj` commands.

* Graph node symbols are now configurable via `ui.graph.default_node` and `ui.graph.elided_node`.
* Graph node symbols are now configurable via templates
* `templates.log_node`
* `templates.op_log_node`
* `templates.log_node_elided`


* `jj log` now includes synthetic nodes in the graph where some revisions were
elided.
Expand Down
8 changes: 8 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2309,6 +2309,14 @@ pub fn parse_args(
Ok((matches, args))
}

pub fn format_template<T>(ui: &Ui, arg: &T, template: &dyn Template<T>) -> String {
let mut output = vec![];
template
.format(arg, ui.new_formatter(&mut output).as_mut())
.expect("write() to vec backed formatter should never fail");
String::from_utf8(output).expect("template output should be utf-8 bytes")
}

/// CLI command builder and runner.
#[must_use]
pub struct CliRunner {
Expand Down
26 changes: 15 additions & 11 deletions cli/src/commands/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ use jj_lib::revset_graph::{
};
use tracing::instrument;

use crate::cli_util::{CommandHelper, LogContentFormat, RevisionArg};
use crate::cli_util::{format_template, CommandHelper, LogContentFormat, RevisionArg};
use crate::command_error::CommandError;
use crate::diff_util::{self, DiffFormatArgs};
use crate::generic_templater::GenericTemplateLanguage;
use crate::graphlog::{get_graphlog, Edge};
use crate::templater::Template;
use crate::ui::Ui;

/// Show revision history
Expand Down Expand Up @@ -94,7 +96,6 @@ pub(crate) fn cmd_log(
expression
};
let repo = workspace_command.repo();
let wc_commit_id = workspace_command.get_wc_commit_id();
let matcher = workspace_command.matcher_from_values(&args.paths)?;
let revset = workspace_command.evaluate_revset(revset_expression)?;

Expand All @@ -113,15 +114,21 @@ pub(crate) fn cmd_log(
let template = workspace_command.parse_commit_template(&template_string)?;
let with_content_format = LogContentFormat::new(ui, command.settings())?;

let commit_node_template =
workspace_command.parse_commit_template(&command.settings().commit_node_template())?;

let elided_node_template = workspace_command.parse_template(
&GenericTemplateLanguage::new(),
&command.settings().elided_node_template(),
)?;

{
ui.request_pager();
let mut formatter = ui.stdout_formatter();
let formatter = formatter.as_mut();

if !args.no_graph {
let mut graph = get_graphlog(command.settings(), formatter.raw());
let default_node_symbol = command.settings().default_node_symbol();
let elided_node_symbol = command.settings().elided_node_symbol();
let forward_iter = TopoGroupedRevsetGraphIterator::new(revset.iter_graph());
let iter: Box<dyn Iterator<Item = _>> = if args.reversed {
Box::new(ReverseRevsetGraphIterator::new(forward_iter))
Expand Down Expand Up @@ -179,16 +186,12 @@ pub(crate) fn cmd_log(
&diff_formats,
)?;
}
let node_symbol = if Some(&key.0) == wc_commit_id {
"@"
} else {
&default_node_symbol
};

let node_symbol = format_template(ui, &commit, commit_node_template.as_ref());
graph.add_node(
&key,
&graphlog_edges,
node_symbol,
&node_symbol,
&String::from_utf8_lossy(&buffer),
)?;
for elided_target in elided_targets {
Expand All @@ -201,10 +204,11 @@ pub(crate) fn cmd_log(
|formatter| writeln!(formatter.labeled("elided"), "(elided revisions)"),
|| graph.width(&elided_key, &edges),
)?;
let node_symbol = format_template(ui, &(), elided_node_template.as_ref());
graph.add_node(
&elided_key,
&edges,
&elided_node_symbol,
&node_symbol,
&String::from_utf8_lossy(&buffer),
)?;
}
Expand Down
17 changes: 8 additions & 9 deletions cli/src/commands/obslog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ use jj_lib::matchers::EverythingMatcher;
use jj_lib::rewrite::rebase_to_dest_parent;
use tracing::instrument;

use crate::cli_util::{CommandHelper, LogContentFormat, RevisionArg, WorkspaceCommandHelper};
use crate::cli_util::{
format_template, CommandHelper, LogContentFormat, RevisionArg, WorkspaceCommandHelper,
};
use crate::command_error::CommandError;
use crate::diff_util::{self, DiffFormat, DiffFormatArgs};
use crate::formatter::Formatter;
Expand Down Expand Up @@ -63,7 +65,6 @@ pub(crate) fn cmd_obslog(
let workspace_command = command.workspace_helper(ui)?;

let start_commit = workspace_command.resolve_single_rev(&args.revision)?;
let wc_commit_id = workspace_command.get_wc_commit_id();

let diff_formats =
diff_util::diff_formats_for_log(command.settings(), &args.diff_format, args.patch)?;
Expand All @@ -80,6 +81,9 @@ pub(crate) fn cmd_obslog(
let formatter = formatter.as_mut();
formatter.push_label("log")?;

let commit_node_template =
workspace_command.parse_commit_template(&command.settings().commit_node_template())?;

let mut commits = topo_order_reverse(
vec![start_commit],
|commit: &Commit| commit.id().clone(),
Expand All @@ -90,7 +94,6 @@ pub(crate) fn cmd_obslog(
}
if !args.no_graph {
let mut graph = get_graphlog(command.settings(), formatter.raw());
let default_node_symbol = command.settings().default_node_symbol();
for commit in commits {
let mut edges = vec![];
for predecessor in &commit.predecessors() {
Expand All @@ -115,15 +118,11 @@ pub(crate) fn cmd_obslog(
&diff_formats,
)?;
}
let node_symbol = if Some(commit.id()) == wc_commit_id {
"@"
} else {
&default_node_symbol
};
let node_symbol = format_template(ui, &commit, commit_node_template.as_ref());
graph.add_node(
commit.id(),
&edges,
node_symbol,
&node_symbol,
&String::from_utf8_lossy(&buffer),
)?;
}
Expand Down
23 changes: 13 additions & 10 deletions cli/src/commands/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ use jj_lib::op_walk;
use jj_lib::operation::Operation;
use jj_lib::repo::Repo;

use crate::cli_util::{short_operation_hash, CommandHelper, LogContentFormat};
use crate::cli_util::{format_template, short_operation_hash, CommandHelper, LogContentFormat};
use crate::command_error::{user_error, user_error_with_hint, CommandError};
use crate::graphlog::{get_graphlog, Edge};
use crate::operation_templater::OperationTemplateLanguage;
use crate::templater::Template as _;
use crate::templater::Template;
use crate::ui::Ui;

/// Commands for working with the operation log
Expand Down Expand Up @@ -172,20 +172,27 @@ fn cmd_op_log(
};
let with_content_format = LogContentFormat::new(ui, command.settings())?;

let op_node_template = {
let language = OperationTemplateLanguage::new(
repo_loader.op_store().root_operation_id(),
current_op_id,
command.operation_template_extension(),
);
command.parse_template(ui, &language, &command.settings().op_node_template())?
};

ui.request_pager();
let mut formatter = ui.stdout_formatter();
let formatter = formatter.as_mut();
let iter = op_walk::walk_ancestors(&head_ops).take(args.limit.unwrap_or(usize::MAX));
if !args.no_graph {
let mut graph = get_graphlog(command.settings(), formatter.raw());
let default_node_symbol = command.settings().default_node_symbol();
for op in iter {
let op = op?;
let mut edges = vec![];
for id in op.parent_ids() {
edges.push(Edge::Direct(id.clone()));
}
let is_current_op = Some(op.id()) == current_op_id;
let mut buffer = vec![];
with_content_format.write_graph_text(
ui.new_formatter(&mut buffer).as_mut(),
Expand All @@ -197,15 +204,11 @@ fn cmd_op_log(
if !buffer.ends_with(b"\n") {
buffer.push(b'\n');
}
let node_symbol = if is_current_op {
"@"
} else {
&default_node_symbol
};
let node_symbol = format_template(ui, &op, &op_node_template);
graph.add_node(
op.id(),
&edges,
node_symbol,
&node_symbol,
&String::from_utf8_lossy(&buffer),
)?;
}
Expand Down
8 changes: 0 additions & 8 deletions cli/src/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,6 @@
"ascii-large"
],
"default": "curved"
},
"default_node": {
"type": "string",
"description": "The symbol used as the default symbol for nodes."
},
"elided_node": {
"type": "string",
"description": "The symbol used for elided nodes."
}
}
},
Expand Down
22 changes: 11 additions & 11 deletions cli/tests/test_log_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,7 @@ fn test_elided() {
}

#[test]
fn test_custom_symbols() {
fn test_log_with_custom_symbols() {
// Test that elided commits are shown as synthetic nodes.
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
Expand All @@ -1439,11 +1439,11 @@ fn test_custom_symbols() {
// Simple test with showing default and elided nodes.
test_env.add_config(concat!(
"ui.log-synthetic-elided-nodes = true\n",
"ui.graph.default_node = ''\n",
"ui.graph.elided_node = '🮀'",
"templates.log_node = 'if(current_working_copy, \"$\", if(root, \"\", \"\"))'\n",
"templates.log_node_elided = '\"🮀\"'",
));
insta::assert_snapshot!(get_log("@ | @- | description(initial)"), @r###"
@ merge
insta::assert_snapshot!(get_log("@ | @- | description(initial) | root()"), @r###"
$ merge
├─╮
│ ┝ side branch 2
│ │
Expand All @@ -1454,18 +1454,18 @@ fn test_custom_symbols() {
├─╯
┝ initial
~
"###);

// Simple test with showing default and elided nodes, ascii style.
test_env.add_config(concat!(
"ui.log-synthetic-elided-nodes = true\n",
"ui.graph.style = 'ascii'\n",
"ui.graph.default_node = '*'\n",
"ui.graph.elided_node = ':'",
"templates.log_node = 'if(current_working_copy, \"$\", if(root, \"^\", \"*\"))'\n",
"templates.log_node_elided = '\":\"'",
));
insta::assert_snapshot!(get_log("@ | @- | description(initial)"), @r###"
@ merge
insta::assert_snapshot!(get_log("@ | @- | description(initial) | root()"), @r###"
$ merge
|\
| * side branch 2
| |
Expand All @@ -1476,6 +1476,6 @@ fn test_custom_symbols() {
|/
* initial
|
~
^
"###);
}
32 changes: 32 additions & 0 deletions cli/tests/test_obslog_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,38 @@ fn test_obslog_with_or_without_diff() {
"###);
}

#[test]
fn test_obslog_with_custom_symbols() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");

std::fs::write(repo_path.join("file1"), "foo\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new", "-m", "my description"]);
std::fs::write(repo_path.join("file1"), "foo\nbar\n").unwrap();
std::fs::write(repo_path.join("file2"), "foo\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["rebase", "-r", "@", "-d", "root()"]);
std::fs::write(repo_path.join("file1"), "resolved\n").unwrap();

let toml = concat!(
"templates.log_node_elided = true\n",
"templates.log_node = 'if(current_working_copy, \"$\", if(root, \"\", \"\"))'\n",
);

let stdout = test_env.jj_cmd_success(&repo_path, &["obslog", "--config-toml", toml]);

insta::assert_snapshot!(stdout, @r###"
$ rlvkpnrz [email protected] 2001-02-03 04:05:10.000 +07:00 66b42ad3
│ my description
┝ rlvkpnrz hidden [email protected] 2001-02-03 04:05:09.000 +07:00 ebc23d4b conflict
│ my description
┝ rlvkpnrz hidden [email protected] 2001-02-03 04:05:09.000 +07:00 6fbba7bc
│ my description
┝ rlvkpnrz hidden [email protected] 2001-02-03 04:05:08.000 +07:00 eac0d0da
(empty) my description
"###);
}

#[test]
fn test_obslog_word_wrap() {
let test_env = TestEnvironment::default();
Expand Down
31 changes: 31 additions & 0 deletions cli/tests/test_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,37 @@ fn test_op_log() {
"###);
}

#[test]
fn test_op_log_with_custom_symbols() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "description 0"]);

let stdout = test_env.jj_cmd_success(
&repo_path,
&[
"op",
"log",
"--config-toml",
concat!(
"template-aliases.'format_time_range(x)' = 'x'\n",
"templates.op_log_node = 'if(current_operation, \"$\", if(root, \"\", \"\"))'",
),
],
);
insta::assert_snapshot!(&stdout, @r###"
$ 52ac15d375ba [email protected] 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
│ describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22
│ args: jj describe -m 'description 0'
┝ b51416386f26 [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ add workspace 'default'
┝ 9a7d829846af [email protected] 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
│ initialize repo
┴ 000000000000 root()
"###);
}

#[test]
fn test_op_log_with_no_template() {
let test_env = TestEnvironment::default();
Expand Down
Loading

0 comments on commit 527bd2f

Please sign in to comment.