jj/cli/tests/test_operations.rs
Aleksey Kuznetsov 38d14eafe2 cli: enrich the error about required template value with a hint
Several `jj` commands accept `--template <TEMPLATE>` argument. When the argument
is empty, `jj` will show the list of defined template aliases.
2024-03-06 08:12:40 +05:00

474 lines
21 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2022 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::path::Path;
use itertools::Itertools;
use regex::Regex;
use crate::common::{get_stdout_string, TestEnvironment};
#[test]
fn test_op_log() {
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",
"template-aliases.'format_time_range(x)' = 'x'",
],
);
insta::assert_snapshot!(&stdout, @r###"
@ 52ac15d375ba 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'
◉ 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()
"###);
let op_log_lines = stdout.lines().collect_vec();
let add_workspace_id = op_log_lines[3].split(' ').nth(2).unwrap();
let initialize_repo_id = op_log_lines[5].split(' ').nth(2).unwrap();
// Can load the repo at a specific operation ID
insta::assert_snapshot!(get_log_output(&test_env, &repo_path, initialize_repo_id), @r###"
◉ 0000000000000000000000000000000000000000
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path, add_workspace_id), @r###"
@ 230dd059e1b059aefc0da06a2e5a7dbf22362f22
◉ 0000000000000000000000000000000000000000
"###);
// "@" resolves to the head operation
insta::assert_snapshot!(get_log_output(&test_env, &repo_path, "@"), @r###"
@ bc8f18aa6f396a93572811632313cbb5625d475d
◉ 0000000000000000000000000000000000000000
"###);
// "@-" resolves to the parent of the head operation
insta::assert_snapshot!(get_log_output(&test_env, &repo_path, "@-"), @r###"
@ 230dd059e1b059aefc0da06a2e5a7dbf22362f22
◉ 0000000000000000000000000000000000000000
"###);
insta::assert_snapshot!(
test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@----"]), @r###"
Error: The "@----" expression resolved to no operations
"###);
// We get a reasonable message if an invalid operation ID is specified
insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "foo"]), @r###"
Error: Operation ID "foo" is not a valid hexadecimal prefix
"###);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "description 1"]);
test_env.jj_cmd_ok(
&repo_path,
&[
"describe",
"-m",
"description 2",
"--at-op",
add_workspace_id,
],
);
insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@-"]), @r###"
Error: The "@" expression resolved to more than one operation
"###);
}
#[test]
fn test_op_log_with_no_template() {
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");
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["op", "log", "-T"]);
insta::assert_snapshot!(stderr, @r###"
error: a value is required for '--template <TEMPLATE>' but none was supplied
For more information, try '--help'.
Hint: The following template aliases are defined:
- builtin_change_id_with_hidden_and_divergent_info
- builtin_log_comfortable
- builtin_log_compact
- builtin_log_detailed
- builtin_log_oneline
- builtin_op_log_comfortable
- builtin_op_log_compact
- commit_summary_separator
- description_placeholder
- email_placeholder
- name_placeholder
"###);
}
#[test]
fn test_op_log_limit() {
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");
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "-Tdescription", "--limit=1"]);
insta::assert_snapshot!(stdout, @r###"
@ add workspace 'default'
"###);
}
#[test]
fn test_op_log_no_graph() {
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");
let stdout =
test_env.jj_cmd_success(&repo_path, &["op", "log", "--no-graph", "--color=always"]);
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'
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()
"###);
}
#[test]
fn test_op_log_no_graph_null_terminated() {
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, &["commit", "-m", "message1"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "message2"]);
let stdout = test_env.jj_cmd_success(
&repo_path,
&[
"op",
"log",
"--no-graph",
"--template",
r#"id.short(4) ++ "\0""#,
],
);
insta::assert_debug_snapshot!(stdout, @r###""22d4\023da\0b514\09a7d\00000\0""###);
}
#[test]
fn test_op_log_template() {
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");
let render = |template| test_env.jj_cmd_success(&repo_path, &["op", "log", "-T", template]);
insta::assert_snapshot!(render(r#"id ++ "\n""#), @r###"
@ b51416386f2685fd5493f2b20e8eec3c24a1776d9e1a7cb5ed7e30d2d9c88c0c1e1fe71b0b7358cba60de42533d1228ed9878f2f89817d892c803395ccf9fe92
◉ 9a7d829846af88a2f7a1e348fb46ff58729e49632bc9c6a052aec8501563cb0d10f4a4e6010ffde529f84a2b9b5b3a4c211a889106a41f6c076dfdacc79f6af7
◉ 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
"###);
insta::assert_snapshot!(
render(r#"separate(" ", id.short(5), current_operation, user,
time.start(), time.end(), time.duration()) ++ "\n""#), @r###"
@ b5141 true test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 2001-02-03 04:05:07.000 +07:00 less than a microsecond
◉ 9a7d8 false test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 2001-02-03 04:05:07.000 +07:00 less than a microsecond
◉ 00000 false @ 1970-01-01 00:00:00.000 +00:00 1970-01-01 00:00:00.000 +00:00 less than a microsecond
"###);
// Negative length shouldn't cause panic.
insta::assert_snapshot!(render(r#"id.short(-1) ++ "|""#), @r###"
@ <Error: out of range integral type conversion attempted>|
◉ <Error: out of range integral type conversion attempted>|
◉ <Error: out of range integral type conversion attempted>|
"###);
// Test the default template, i.e. with relative start time and duration. We
// don't generally use that template because it depends on the current time,
// so we need to reset the time range format here.
test_env.add_config(
r#"
[template-aliases]
'format_time_range(time_range)' = 'time_range.start().ago() ++ ", lasted " ++ time_range.duration()'
"#,
);
let regex = Regex::new(r"\d\d years").unwrap();
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
insta::assert_snapshot!(regex.replace_all(&stdout, "NN years"), @r###"
@ b51416386f26 test-username@host.example.com NN years ago, lasted less than a microsecond
│ add workspace 'default'
◉ 9a7d829846af test-username@host.example.com NN years ago, lasted less than a microsecond
│ initialize repo
◉ 000000000000 root()
"###);
}
#[test]
fn test_op_log_builtin_templates() {
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");
let render = |template| test_env.jj_cmd_success(&repo_path, &["op", "log", "-T", template]);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "description 0"]);
insta::assert_snapshot!(render(r#"builtin_op_log_compact"#), @r###"
@ 52ac15d375ba 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'
◉ 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(r#"builtin_op_log_comfortable"#), @r###"
@ 52ac15d375ba 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'
◉ 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()
"###);
}
#[test]
fn test_op_log_word_wrap() {
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");
let render = |args: &[&str], columns: u32, word_wrap: bool| {
let mut args = args.to_vec();
if word_wrap {
args.push("--config-toml=ui.log-word-wrap=true");
}
let assert = test_env
.jj_cmd(&repo_path, &args)
.env("COLUMNS", columns.to_string())
.assert()
.success()
.stderr("");
get_stdout_string(&assert)
};
// 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
│ 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
│ 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()
"###);
}
#[test]
fn test_op_log_configurable() {
let test_env = TestEnvironment::default();
test_env.add_config(
r#"operation.hostname = "my-hostname"
operation.username = "my-username"
"#,
);
test_env
.jj_cmd(test_env.env_root(), &["init", "repo", "--git"])
.env_remove("JJ_OP_HOSTNAME")
.env_remove("JJ_OP_USERNAME")
.assert()
.success();
let repo_path = test_env.env_root().join("repo");
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
assert!(stdout.contains("my-username@my-hostname"));
}
#[test]
fn test_op_abandon_ancestors() {
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, &["commit", "-m", "commit 1"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "commit 2"]);
insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log"]), @r###"
@ de138472a722 test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00
│ commit a8ac27b29a157ae7dabc0deb524df68823505730
│ args: jj commit -m 'commit 2'
◉ 652e5aecc9f7 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
│ commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22
│ args: jj commit -m 'commit 1'
◉ 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()
"###);
// Abandon old operations. The working-copy operation id should be updated.
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "abandon", "..@-"]);
insta::assert_snapshot!(stderr, @r###"
Abandoned 3 operations and reparented 1 descendant operations.
"###);
insta::assert_snapshot!(
test_env.jj_cmd_success(&repo_path, &["debug", "workingcopy", "--ignore-working-copy"]), @r###"
Current operation: OperationId("10e856d0579c4aca88972ffea0a515f47f8c09dceaa2b1a5d531c83b04350f7aa64a3eff422def01eebd65df26089ef5e0f925f247a8f929c8cc858d16306e53")
Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))
"###);
insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log"]), @r###"
@ 10e856d0579c test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00
│ commit a8ac27b29a157ae7dabc0deb524df68823505730
│ args: jj commit -m 'commit 2'
◉ 000000000000 root()
"###);
// Abandon operation range.
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "commit 3"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "commit 4"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "commit 5"]);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "abandon", "@---..@-"]);
insta::assert_snapshot!(stderr, @r###"
Abandoned 2 operations and reparented 1 descendant operations.
"###);
insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log"]), @r###"
@ 70112b4447b6 test-username@host.example.com 2001-02-03 04:05:16.000 +07:00 - 2001-02-03 04:05:16.000 +07:00
│ commit e184d62c9ab118b0f62de91959b857550a9273a5
│ args: jj commit -m 'commit 5'
◉ 10e856d0579c test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00
│ commit a8ac27b29a157ae7dabc0deb524df68823505730
│ args: jj commit -m 'commit 2'
◉ 000000000000 root()
"###);
// Can't abandon the current operation.
let stderr = test_env.jj_cmd_failure(&repo_path, &["op", "abandon", "..@"]);
insta::assert_snapshot!(stderr, @r###"
Error: Cannot abandon the current operation
Hint: Run `jj undo` to revert the current operation, then use `jj op abandon`
"###);
// Can't create concurrent abandoned operations explicitly.
let stderr = test_env.jj_cmd_failure(&repo_path, &["op", "abandon", "--at-op=@-", "@"]);
insta::assert_snapshot!(stderr, @r###"
Error: --at-op is not respected
"###);
// Abandon the current operation by undoing it first.
test_env.jj_cmd_ok(&repo_path, &["undo"]);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "abandon", "@-"]);
insta::assert_snapshot!(stderr, @r###"
Abandoned 1 operations and reparented 1 descendant operations.
"###);
insta::assert_snapshot!(
test_env.jj_cmd_success(&repo_path, &["debug", "workingcopy", "--ignore-working-copy"]), @r###"
Current operation: OperationId("445e93662d714f53fb97e450eb3793a8e1a9a4cca1e329d5a9096e65085fb96d5bb51659531145c8abf0db5da803cd11c4cecdcf9025391bc97fc6a42204a0fe")
Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))
"###);
insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log"]), @r###"
@ 445e93662d71 test-username@host.example.com 2001-02-03 04:05:21.000 +07:00 - 2001-02-03 04:05:21.000 +07:00
│ undo operation 70112b4447b65fa811038b2b119fe22e959e3b3194b461a32475f6528c2b684ac6baebc86cce7ad7e0bb92c033852850e561506508ca43e823626f107e81ed76
│ args: jj undo
◉ 10e856d0579c test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00
│ commit a8ac27b29a157ae7dabc0deb524df68823505730
│ args: jj commit -m 'commit 2'
◉ 000000000000 root()
"###);
// Abandon empty range.
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "abandon", "@-..@-"]);
insta::assert_snapshot!(stderr, @r###"
Nothing changed.
"###);
insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log", "-l1"]), @r###"
@ 445e93662d71 test-username@host.example.com 2001-02-03 04:05:21.000 +07:00 - 2001-02-03 04:05:21.000 +07:00
│ undo operation 70112b4447b65fa811038b2b119fe22e959e3b3194b461a32475f6528c2b684ac6baebc86cce7ad7e0bb92c033852850e561506508ca43e823626f107e81ed76
│ args: jj undo
"###);
}
#[test]
fn test_op_abandon_without_updating_working_copy() {
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, &["commit", "-m", "commit 1"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "commit 2"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "commit 3"]);
// Abandon without updating the working copy.
let (_stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["op", "abandon", "@-", "--ignore-working-copy"],
);
insta::assert_snapshot!(stderr, @r###"
Abandoned 1 operations and reparented 1 descendant operations.
"###);
insta::assert_snapshot!(
test_env.jj_cmd_success(&repo_path, &["debug", "workingcopy", "--ignore-working-copy"]), @r###"
Current operation: OperationId("61aeade2493b190412c61b9b0711025c2cccf95966870a5f7dc2e34ab313fbef64da70d00cab2cd69ad39816faeb80a6bc866927549b84dce6cb5a437e0e515b")
Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))
"###);
insta::assert_snapshot!(
test_env.jj_cmd_success(&repo_path, &["op", "log", "-l1", "--ignore-working-copy"]), @r###"
@ ae6364994418 test-username@host.example.com 2001-02-03 04:05:10.000 +07:00 - 2001-02-03 04:05:10.000 +07:00
│ commit 268f5f16139313ff25bef31280b2ec2e675200f3
│ args: jj commit -m 'commit 3'
"###);
// The working-copy operation id isn't updated if it differs from the repo.
// It could be updated if the tree matches, but there's no extra logic for
// that.
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "abandon", "@-"]);
insta::assert_snapshot!(stderr, @r###"
Abandoned 1 operations and reparented 1 descendant operations.
The working copy operation 61aeade2493b is not updated because it differs from the repo ae6364994418.
"###);
insta::assert_snapshot!(
test_env.jj_cmd_success(&repo_path, &["debug", "workingcopy", "--ignore-working-copy"]), @r###"
Current operation: OperationId("61aeade2493b190412c61b9b0711025c2cccf95966870a5f7dc2e34ab313fbef64da70d00cab2cd69ad39816faeb80a6bc866927549b84dce6cb5a437e0e515b")
Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))
"###);
insta::assert_snapshot!(
test_env.jj_cmd_success(&repo_path, &["op", "log", "-l1", "--ignore-working-copy"]), @r###"
@ 51192a90e899 test-username@host.example.com 2001-02-03 04:05:10.000 +07:00 - 2001-02-03 04:05:10.000 +07:00
│ commit 268f5f16139313ff25bef31280b2ec2e675200f3
│ args: jj commit -m 'commit 3'
"###);
}
fn get_log_output(test_env: &TestEnvironment, repo_path: &Path, op_id: &str) -> String {
test_env.jj_cmd_success(
repo_path,
&["log", "-T", "commit_id", "--at-op", op_id, "-r", "all()"],
)
}