ok/jj
1
0
Fork 0
forked from mirrors/jj

op log: add --op-diff option to embed operation diffs

This is basically "log -p" for "op log". The flag name has "op" because --diff
and --patch mean a similar thing in this context. Since -p implies --op-diff,
user can just do "op log -p" if he's okay with verbose op + content diffs.
Note that --no-graph affects both "op log" and "op diff" parts.

We might want to do some style changes later, such as inserting/deleting blank
lines, highlighting headers, etc.
This commit is contained in:
Yuya Nishihara 2024-09-03 17:58:46 +09:00
parent 050cde94ef
commit 78edc6aba5
5 changed files with 340 additions and 12 deletions

View file

@ -47,6 +47,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
(inherit from parent; default), `full` (full working copy), or `empty` (the
empty working copy).
* `jj op log` gained an option to include operation diffs.
### Fixed bugs
* Fixed panic when parsing invalid conflict markers of a particular form.

View file

@ -902,6 +902,10 @@ impl WorkspaceCommandHelper {
self.workspace.repo_path()
}
pub fn workspace(&self) -> &Workspace {
&self.workspace
}
pub fn working_copy(&self) -> &dyn WorkingCopy {
self.workspace.working_copy()
}

View file

@ -14,16 +14,24 @@
use std::slice;
use itertools::Itertools as _;
use jj_lib::op_walk;
use jj_lib::operation::Operation;
use jj_lib::repo::RepoLoader;
use jj_lib::settings::ConfigResultExt as _;
use jj_lib::settings::UserSettings;
use super::diff::show_op_diff;
use crate::cli_util::format_template;
use crate::cli_util::CommandHelper;
use crate::cli_util::LogContentFormat;
use crate::cli_util::WorkspaceCommandEnvironment;
use crate::command_error::CommandError;
use crate::commit_templater::CommitTemplateLanguage;
use crate::diff_util::diff_formats_for_log;
use crate::diff_util::DiffFormatArgs;
use crate::diff_util::DiffRenderer;
use crate::formatter::Formatter;
use crate::graphlog::get_graphlog;
use crate::graphlog::Edge;
use crate::graphlog::GraphStyle;
@ -56,6 +64,18 @@ pub struct OperationLogArgs {
/// For the syntax, see https://martinvonz.github.io/jj/latest/templates/
#[arg(long, short = 'T')]
template: Option<String>,
/// Show changes to the repository at each operation
#[arg(long)]
op_diff: bool,
/// Show patch of modifications to changes (implies --op-diff)
///
/// If the previous version has different parents, it will be temporarily
/// rebased to the parents of the new version, so the diff is not
/// contaminated by unrelated changes.
#[arg(long, short = 'p')]
patch: bool,
#[command(flatten)]
diff_format: DiffFormatArgs,
}
pub fn cmd_op_log(
@ -66,27 +86,28 @@ pub fn cmd_op_log(
if command.is_working_copy_writable() {
let workspace_command = command.workspace_helper(ui)?;
let current_op = workspace_command.repo().operation();
do_op_log(ui, workspace_command.env(), current_op, args)
let repo_loader = workspace_command.workspace().repo_loader();
do_op_log(ui, workspace_command.env(), repo_loader, current_op, args)
} else {
// Don't load the repo so that the operation history can be inspected
// even with a corrupted repo state. For example, you can find the first
// bad operation id to be abandoned.
let workspace = command.load_workspace()?;
let workspace_env = command.workspace_environment(ui, &workspace)?;
let repo_loader = workspace.repo_loader();
let current_op = command.resolve_operation(ui, workspace.repo_loader())?;
do_op_log(ui, &workspace_env, &current_op, args)
do_op_log(ui, &workspace_env, repo_loader, &current_op, args)
}
}
fn do_op_log(
ui: &mut Ui,
workspace_env: &WorkspaceCommandEnvironment,
repo_loader: &RepoLoader,
current_op: &Operation,
args: &OperationLogArgs,
) -> Result<(), CommandError> {
let settings = workspace_env.settings();
let op_store = current_op.op_store();
let graph_style = GraphStyle::from_settings(settings)?;
let with_content_format = LogContentFormat::new(ui, settings)?;
@ -94,7 +115,7 @@ fn do_op_log(
let op_node_template;
{
let language = OperationTemplateLanguage::new(
op_store.root_operation_id(),
repo_loader.op_store().root_operation_id(),
Some(current_op.id()),
workspace_env.operation_template_extensions(),
);
@ -114,6 +135,49 @@ fn do_op_log(
.labeled("node");
}
let diff_formats = diff_formats_for_log(settings, &args.diff_format, args.patch)?;
let maybe_show_op_diff = if args.op_diff || !diff_formats.is_empty() {
let template_text = settings.config().get_string("templates.commit_summary")?;
let show = move |ui: &Ui,
formatter: &mut dyn Formatter,
op: &Operation,
with_content_format: &LogContentFormat| {
let parents: Vec<_> = op.parents().try_collect()?;
let parent_op = repo_loader.merge_operations(settings, parents, None)?;
let parent_repo = repo_loader.load_at(&parent_op)?;
let repo = repo_loader.load_at(op)?;
let id_prefix_context = workspace_env.new_id_prefix_context();
let commit_summary_template = {
let language =
workspace_env.commit_template_language(repo.as_ref(), &id_prefix_context);
workspace_env.parse_template(
&language,
&template_text,
CommitTemplateLanguage::wrap_commit,
)?
};
let path_converter = workspace_env.path_converter();
let diff_renderer = (!diff_formats.is_empty())
.then(|| DiffRenderer::new(repo.as_ref(), path_converter, diff_formats.clone()));
show_op_diff(
ui,
formatter,
repo.as_ref(),
&parent_repo,
&repo,
&commit_summary_template,
(!args.no_graph).then_some(graph_style),
with_content_format,
diff_renderer.as_ref(),
)
};
Some(show)
} else {
None
};
ui.request_pager();
let mut formatter = ui.stdout_formatter();
let formatter = formatter.as_mut();
@ -141,6 +205,10 @@ fn do_op_log(
if !buffer.ends_with(b"\n") {
buffer.push(b'\n');
}
if let Some(show) = &maybe_show_op_diff {
let mut formatter = ui.new_formatter(&mut buffer);
show(ui, formatter.as_mut(), &op, &within_graph)?;
}
let node_symbol = format_template(ui, &op, &op_node_template);
graph.add_node(
op.id(),
@ -153,6 +221,9 @@ fn do_op_log(
for op in iter {
let op = op?;
with_content_format.write(formatter, |formatter| template.format(&op, formatter))?;
if let Some(show) = &maybe_show_op_diff {
show(ui, formatter, &op, &with_content_format)?;
}
}
}

View file

@ -1390,6 +1390,22 @@ Like other commands, `jj op log` snapshots the current working-copy changes and
* `-T`, `--template <TEMPLATE>` — Render each operation using the given template
For the syntax, see https://martinvonz.github.io/jj/latest/templates/
* `--op-diff` — Show changes to the repository at each operation
* `-p`, `--patch` — Show patch of modifications to changes (implies --op-diff)
If the previous version has different parents, it will be temporarily rebased to the parents of the new version, so the diff is not contaminated by unrelated changes.
* `-s`, `--summary` — For each path, show only whether it was modified, added, or deleted
* `--stat` — Show a histogram of the changes
* `--types` — For each path, show only its type before and after
The diff is shown as two letters. The first letter indicates the type before and the second letter indicates the type after. '-' indicates that the path was not present, 'F' represents a regular file, `L' represents a symlink, 'C' represents a conflict, and 'G' represents a Git submodule.
* `--name-only` — For each path, show only its path
Typically useful for shell commands like: `jj diff -r @- --name_only | xargs perl -pi -e's/OLD/NEW/g`
* `--git` — Show a Git-format diff
* `--color-words` — Show a word-level diff with changes indicated only by color
* `--tool <TOOL>` — Generate diff by external command
* `--context <CONTEXT>` — Number of lines of context to show

View file

@ -79,6 +79,31 @@ fn test_op_log() {
Error: Operation ID "foo" is not a valid hexadecimal prefix
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "--op-diff"]);
insta::assert_snapshot!(&stdout, @r#"
@ c1851f1c3d90 test-username@host.example.com 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'
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm 19611c99 (empty) description 0
- qpvuntsm hidden 230dd059 (empty) (no description set)
b51416386f26 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm 230dd059 (empty) (no description set)
9a7d829846af test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
Changed commits:
Change zzzzzzzzzzzz
+ zzzzzzzz 00000000 (empty) (no description set)
000000000000 root()
"#);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "description 1"]);
test_env.jj_cmd_ok(
&repo_path,
@ -92,7 +117,7 @@ fn test_op_log() {
);
insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@-"]), @r###"
Error: The "@" expression resolved to more than one operation
Hint: Try specifying one of the operations by ID: 5f690688f7d7, cfb67eb2b65c
Hint: Try specifying one of the operations by ID: c05f56726c27, f9fdb4e100bd
"###);
}
@ -183,6 +208,23 @@ fn test_op_log_no_graph() {
initialize repo
000000000000 root()
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "--op-diff", "--no-graph"]);
insta::assert_snapshot!(&stdout, @r#"
b51416386f26 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm 230dd059 (empty) (no description set)
9a7d829846af test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
Changed commits:
Change zzzzzzzzzzzz
+ zzzzzzzz 00000000 (empty) (no description set)
000000000000 root()
"#);
}
#[test]
@ -299,6 +341,9 @@ fn test_op_log_word_wrap() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file1"), "foo\n".repeat(100)).unwrap();
test_env.jj_cmd_ok(&repo_path, &["debug", "snapshot"]);
let render = |args: &[&str], columns: u32, word_wrap: bool| {
let mut args = args.to_vec();
if word_wrap {
@ -314,15 +359,24 @@ fn test_op_log_word_wrap() {
};
// ui.log-word-wrap option works
insta::assert_snapshot!(render(&["op", "log"], 40, false), @r###"
@ b51416386f26 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
insta::assert_snapshot!(render(&["op", "log"], 40, false), @r#"
@ cf25045e0079 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
snapshot working copy
args: jj debug snapshot
b51416386f26 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
9a7d829846af test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
000000000000 root()
"###);
insta::assert_snapshot!(render(&["op", "log"], 40, true), @r###"
@ b51416386f26
"#);
insta::assert_snapshot!(render(&["op", "log"], 40, true), @r#"
@ cf25045e0079
test-username@host.example.com
2001-02-03 04:05:08.000 +07:00 -
2001-02-03 04:05:08.000 +07:00
snapshot working copy
args: jj debug snapshot
b51416386f26
test-username@host.example.com
2001-02-03 04:05:07.000 +07:00 -
2001-02-03 04:05:07.000 +07:00
@ -333,7 +387,98 @@ fn test_op_log_word_wrap() {
2001-02-03 04:05:07.000 +07:00
initialize repo
000000000000 root()
"###);
"#);
// Nested graph should be wrapped
insta::assert_snapshot!(render(&["op", "log", "--op-diff"], 40, true), @r#"
@ cf25045e0079
test-username@host.example.com
2001-02-03 04:05:08.000 +07:00 -
2001-02-03 04:05:08.000 +07:00
snapshot working copy
args: jj debug snapshot
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm e292def1 (no
description set)
- qpvuntsm hidden 230dd059 (empty)
(no description set)
b51416386f26
test-username@host.example.com
2001-02-03 04:05:07.000 +07:00 -
2001-02-03 04:05:07.000 +07:00
add workspace 'default'
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm 230dd059 (empty) (no
description set)
9a7d829846af
test-username@host.example.com
2001-02-03 04:05:07.000 +07:00 -
2001-02-03 04:05:07.000 +07:00
initialize repo
Changed commits:
Change zzzzzzzzzzzz
+ zzzzzzzz 00000000 (empty) (no
description set)
000000000000 root()
"#);
// Nested diff stat shouldn't exceed the terminal width
insta::assert_snapshot!(render(&["op", "log", "-n1", "--stat"], 40, true), @r#"
@ cf25045e0079
test-username@host.example.com
2001-02-03 04:05:08.000 +07:00 -
2001-02-03 04:05:08.000 +07:00
snapshot working copy
args: jj debug snapshot
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm e292def1 (no
description set)
- qpvuntsm hidden 230dd059 (empty)
(no description set)
file1 | 100 +++++++++++++++++++
1 file changed, 100 insertions(+), 0 deletions(-)
"#);
insta::assert_snapshot!(render(&["op", "log", "-n1", "--no-graph", "--stat"], 40, true), @r#"
cf25045e0079
test-username@host.example.com
2001-02-03 04:05:08.000 +07:00 -
2001-02-03 04:05:08.000 +07:00
snapshot working copy
args: jj debug snapshot
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm e292def1 (no description set)
- qpvuntsm hidden 230dd059 (empty) (no
description set)
file1 | 100 +++++++++++++++++++++++++
1 file changed, 100 insertions(+), 0 deletions(-)
"#);
// Nested graph widths should be subtracted from the term width
let config = r#"templates.commit_summary='"0 1 2 3 4 5 6 7 8 9"'"#;
insta::assert_snapshot!(
render(&["op", "log", "-T''", "--op-diff", "-n1", "--config-toml", config], 15, true), @r#"
@
Changed
commits:
Change
qpvuntsmwlqt
+ 0 1 2 3
4 5 6 7 8
9
- 0 1 2 3
4 5 6 7 8
9
"#);
}
#[test]
@ -1915,6 +2060,96 @@ fn test_op_show_patch() {
Change mzvwutvlkqwt
- mzvwutvl hidden 9f4fb57f (empty) (no description set)
"###);
// Try again with "op log".
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "--git"]);
insta::assert_snapshot!(&stdout, @r#"
@ e13dc1c7a3b3 test-username@host.example.com 2001-02-03 04:05:13.000 +07:00 - 2001-02-03 04:05:13.000 +07:00
abandon commit 9f4fb57fba25a7b47ce5980a5d9a4766778331e8
args: jj abandon
Changed commits:
Change yqosqzytrlsw
+ yqosqzyt 33f321c4 (empty) (no description set)
Change mzvwutvlkqwt
- mzvwutvl hidden 9f4fb57f (empty) (no description set)
c53f5f1afbc6 test-username@host.example.com 2001-02-03 04:05:11.000 +07:00 - 2001-02-03 04:05:11.000 +07:00
squash commits into 6b1027d2770cd0a39c468e525e52bf8c47e1464a
args: jj squash
Changed commits:
Change mzvwutvlkqwt
+ mzvwutvl 9f4fb57f (empty) (no description set)
Change rlvkpnrzqnoo
- rlvkpnrz hidden 1d7f8f94 (no description set)
diff --git a/file b/file
index 7898192261..6178079822 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-a
+b
Change qpvuntsmwlqt
+ qpvuntsm 2ac85fd1 (no description set)
- qpvuntsm hidden 6b1027d2 (no description set)
diff --git a/file b/file
index 7898192261..6178079822 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-a
+b
874d3a8b4c77 test-username@host.example.com 2001-02-03 04:05:11.000 +07:00 - 2001-02-03 04:05:11.000 +07:00
snapshot working copy
args: jj squash
Changed commits:
Change rlvkpnrzqnoo
+ rlvkpnrz 1d7f8f94 (no description set)
- rlvkpnrz hidden 56950632 (empty) (no description set)
diff --git a/file b/file
index 7898192261..6178079822 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-a
+b
8f6a879bef11 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
new empty commit
args: jj new
Changed commits:
Change rlvkpnrzqnoo
+ rlvkpnrz 56950632 (empty) (no description set)
6188e9d1f7da test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
snapshot working copy
args: jj new
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm 6b1027d2 (no description set)
- qpvuntsm hidden 230dd059 (empty) (no description set)
diff --git a/file b/file
new file mode 100644
index 0000000000..7898192261
--- /dev/null
+++ b/file
@@ -1,0 +1,1 @@
+a
b51416386f26 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
Changed commits:
Change qpvuntsmwlqt
+ qpvuntsm 230dd059 (empty) (no description set)
9a7d829846af test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
Changed commits:
Change zzzzzzzzzzzz
+ zzzzzzzz 00000000 (empty) (no description set)
000000000000 root()
"#);
}
fn init_bare_git_repo(git_repo_path: &Path) -> git2::Repository {