diff --git a/CHANGELOG.md b/CHANGELOG.md index 37dea3f08..8bd133605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `jj branch rename` will now warn if the renamed branch has a remote branch, since those will have to be manually renamed outside of `jj`. +* There's now a virtual root operation, similar to the [virtual root + commit](docs/glossary.md#root-commit). It appears at the end of `jj op log`. + ### Fixed bugs diff --git a/cli/tests/test_concurrent_operations.rs b/cli/tests/test_concurrent_operations.rs index 0c9b48be3..0246f102b 100644 --- a/cli/tests/test_concurrent_operations.rs +++ b/cli/tests/test_concurrent_operations.rs @@ -35,16 +35,18 @@ fn test_concurrent_operation_divergence() { // "op log" doesn't merge the concurrent operations let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]); insta::assert_snapshot!(stdout, @r###" - ◉ a0cb96855e6b test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 + ◉ 31e3dc1f7c87 test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 │ describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22 │ args: jj describe -m 'message 2' --at-op @- - │ ◉ c208376a3fee test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 + │ ◉ e914ad151dae 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 'message 1' - ◉ 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + ◉ 27143b59c690 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' - ◉ b73abeebcf94 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 + ◉ 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); // We should be informed about the concurrent modification @@ -70,16 +72,18 @@ fn test_concurrent_operations_auto_rebase() { test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "initial"]); let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]); insta::assert_snapshot!(stdout, @r###" - @ ec9298123a0d test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 + @ 621f22ef65b4 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 123ed18e4c4c0d77428df41112bc02ffc83fb935 │ args: jj describe -m initial - ◉ 7ba3da0db37d test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 + ◉ 17cb042ae103 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 describe -m initial - ◉ 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + ◉ 27143b59c690 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' - ◉ b73abeebcf94 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 + ◉ 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); let op_id_hex = stdout[3..15].to_string(); @@ -159,6 +163,7 @@ fn test_concurrent_operations_wc_modified() { ◉ snapshot working copy ◉ add workspace 'default' ◉ initialize repo + ◉ "###); } @@ -183,22 +188,24 @@ fn test_concurrent_snapshot_wc_reloadable() { let template = r#"id ++ "\n" ++ description ++ "\n" ++ tags"#; let op_log_stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "-T", template]); insta::assert_snapshot!(op_log_stdout, @r###" - @ 4f927bf9d6694ed9c409828dc0f9f5f64897ba95ac850f63df92572164b5f146cc09c3217481e74aadbc3a581ded02a487dc73911fe03d86cf1b2c6a20bbe8ed + @ d726ace5612d3c99c793f61d5e86583f430fec332859af62a962f08c93cfac1459884371badcdaccbfd7b065c7df191d68fb35f6d537d1c0cc7ed18c70d6f1e5 │ commit 323b414dd255b51375d7f4392b7b2641ffe4289f │ args: jj commit -m 'new child1' - ◉ def64cf8aa9890a8a5f607890cc6017103a1affbff84addd5f0546c9f9be732f6c2348c082cc81a0654928263978e3d9690e684b9a08214c420311e978487f08 + ◉ 5de87db156fa59f084be7beafb2e8921976cef54061d19671f1406f0f4bc3ad114e6bfee8d2e88e8d2614a49652b8f88071c48a9d78ff951dc81fcac8eb592f3 │ snapshot working copy │ args: jj commit -m 'new child1' - ◉ 42881255f9267d7713b5a2621eb1cabfe6654ac77913642522d5dd482b94b2dee321b75cda544b4252cfc4b18a6c43bcd291f9b1e2838e44843c38fba21d49fa + ◉ 1145f7c8a11bf57eb9bf7ee241a72c19e0fa536a35c099d9e72ad25a4475293f48e1f29eab90e31fa8770566199bf384dcb4f44227635c20d1e21ae2b9700303 │ commit 3d918700494a9895696e955b85fa05eb0d314cc6 │ args: jj commit -m initial - ◉ 8816f70c50c1393c80b6538c8833209802d554600de08adac94c1150ef81af323725f4ed2b52c78e6757c9821400de23483dad5f019ea1a1bf7d9ea49f6b91ab + ◉ a10989671b09d46b636fa4dee86182a170f2b6a9d127a785e8d1388b0680affcc4aeed8bba1face7f3fc1637b7f9327f6ac7f4d9384f468333805a215363ccbd │ snapshot working copy │ args: jj commit -m initial - ◉ 90e23f7e6c7ce885df899e0b4990b39a43816549596a834b727d7eed9650cfe917ac2d043518734223fd7537b9d464e556d28e73cdfed7a0aceb592b89fdba4b + ◉ 27143b59c6904046f6be83ad6fe145d819944f9abbd7247ea9c57848d1d2c678ea8265598a156fe8aeef31d24d958bf6cfa0c2eb3afef40bdae2c5e98d73d0ee │ add workspace 'default' - ◉ b73abeebcf94a1528dfeeed335d6f8c8bac623d060254b01fb6b329d0407f1271eb2c16ecf56f6bac5de73dab988a1524dfb98bfff960eba5e54fa01547fea0d - initialize repo + ◉ 0e8aee02e24230c99d6d90d469c582a60fdb2ae8329341bbdb09f4a0beceba1ce7c84fc9ba6c7657d6d275b392b89b825502475ad2501be1ddebd4a09b07668c + │ initialize repo + ◉ 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + "###); let op_log_lines = op_log_stdout.lines().collect_vec(); let current_op_id = op_log_lines[0].split_once(" ").unwrap().1; diff --git a/cli/tests/test_debug_command.rs b/cli/tests/test_debug_command.rs index fdc63dd9a..e60ad43b3 100644 --- a/cli/tests/test_debug_command.rs +++ b/cli/tests/test_debug_command.rs @@ -127,7 +127,7 @@ fn test_debug_operation_id() { let stdout = test_env.jj_cmd_success(&workspace_path, &["debug", "operation", "--display", "id"]); assert_snapshot!(filter_index_stats(&stdout), @r###" - 90e23f7e6c7ce885df899e0b4990b39a43816549596a834b727d7eed9650cfe917ac2d043518734223fd7537b9d464e556d28e73cdfed7a0aceb592b89fdba4b + 27143b59c6904046f6be83ad6fe145d819944f9abbd7247ea9c57848d1d2c678ea8265598a156fe8aeef31d24d958bf6cfa0c2eb3afef40bdae2c5e98d73d0ee "### ); } diff --git a/cli/tests/test_operations.rs b/cli/tests/test_operations.rs index ac9051baa..d1c309525 100644 --- a/cli/tests/test_operations.rs +++ b/cli/tests/test_operations.rs @@ -38,13 +38,15 @@ fn test_op_log() { ], ); insta::assert_snapshot!(&stdout, @r###" - @ 00cc30b6a1b8 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 + @ 826c45dd2457 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' - ◉ 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + ◉ 27143b59c690 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' - ◉ b73abeebcf94 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 + ◉ 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); let op_log_lines = stdout.lines().collect_vec(); let add_workspace_id = op_log_lines[3].split(' ').nth(2).unwrap(); @@ -69,8 +71,8 @@ fn test_op_log() { ◉ 0000000000000000000000000000000000000000 "###); insta::assert_snapshot!( - test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@---"]), @r###" - Error: The "@---" expression resolved to no operations + 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 @@ -115,10 +117,12 @@ fn test_op_log_no_graph() { let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "--no-graph", "--color=always"]); insta::assert_snapshot!(stdout, @r###" - 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + 27143b59c690 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' - b73abeebcf94 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); } @@ -140,7 +144,7 @@ fn test_op_log_no_graph_null_terminated() { r#"id.short(4) ++ "\0""#, ], ); - insta::assert_debug_snapshot!(stdout, @r###""6dec\08542\090e2\0b73a\0""###); + insta::assert_debug_snapshot!(stdout, @r###""f5e4\05ff2\02714\00e8a\00000\0""###); } #[test] @@ -151,14 +155,16 @@ fn test_op_log_template() { let render = |template| test_env.jj_cmd_success(&repo_path, &["op", "log", "-T", template]); insta::assert_snapshot!(render(r#"id ++ "\n""#), @r###" - @ 90e23f7e6c7ce885df899e0b4990b39a43816549596a834b727d7eed9650cfe917ac2d043518734223fd7537b9d464e556d28e73cdfed7a0aceb592b89fdba4b - ◉ b73abeebcf94a1528dfeeed335d6f8c8bac623d060254b01fb6b329d0407f1271eb2c16ecf56f6bac5de73dab988a1524dfb98bfff960eba5e54fa01547fea0d + @ 27143b59c6904046f6be83ad6fe145d819944f9abbd7247ea9c57848d1d2c678ea8265598a156fe8aeef31d24d958bf6cfa0c2eb3afef40bdae2c5e98d73d0ee + ◉ 0e8aee02e24230c99d6d90d469c582a60fdb2ae8329341bbdb09f4a0beceba1ce7c84fc9ba6c7657d6d275b392b89b825502475ad2501be1ddebd4a09b07668c + ◉ 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 "###); insta::assert_snapshot!( render(r#"separate(" ", id.short(5), current_operation, user, time.start(), time.end(), time.duration()) ++ "\n""#), @r###" - @ 90e23 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 - ◉ b73ab 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 + @ 27143 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 + ◉ 0e8ae 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 (and is clamped.) @@ -166,6 +172,7 @@ fn test_op_log_template() { insta::assert_snapshot!(render(r#"id.short(-1) ++ "|""#), @r###" @ | ◉ | + ◉ | "###); // Test the default template, i.e. with relative start time and duration. We @@ -180,10 +187,12 @@ fn test_op_log_template() { 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###" - @ 90e23f7e6c7c test-username@host.example.com NN years ago, lasted less than a microsecond + @ 27143b59c690 test-username@host.example.com NN years ago, lasted less than a microsecond │ add workspace 'default' - ◉ b73abeebcf94 test-username@host.example.com NN years ago, lasted less than a microsecond - initialize repo + ◉ 0e8aee02e242 test-username@host.example.com NN years ago, lasted less than a microsecond + │ initialize repo + ◉ 000000000000 @ NN years ago, lasted less than a microsecond + "###); } @@ -196,25 +205,30 @@ fn test_op_log_builtin_templates() { test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "description 0"]); insta::assert_snapshot!(render(r#"builtin_op_log_compact"#), @r###" - @ 00cc30b6a1b8 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 + @ 826c45dd2457 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' - ◉ 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + ◉ 27143b59c690 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' - ◉ b73abeebcf94 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 + ◉ 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); insta::assert_snapshot!(render(r#"builtin_op_log_comfortable"#), @r###" - @ 00cc30b6a1b8 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 + @ 826c45dd2457 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' │ - ◉ 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + ◉ 27143b59c690 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' │ - ◉ b73abeebcf94 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 + ◉ 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); } @@ -240,22 +254,28 @@ fn test_op_log_word_wrap() { // ui.log-word-wrap option works insta::assert_snapshot!(render(&["op", "log"], 40, false), @r###" - @ 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + @ 27143b59c690 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' - ◉ b73abeebcf94 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 + ◉ 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); insta::assert_snapshot!(render(&["op", "log"], 40, true), @r###" - @ 90e23f7e6c7c + @ 27143b59c690 │ 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' - ◉ b73abeebcf94 - 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 + ◉ 0e8aee02e242 + │ 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 @ 1970-01-01 + 00:00:00.000 +00:00 - 1970-01-01 + 00:00:00.000 +00:00 + "###); } @@ -288,34 +308,36 @@ fn test_op_abandon_ancestors() { 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###" - @ 362b35cdcee0 test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 + @ 85137561ef60 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' - ◉ b46d00bc0741 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 + ◉ db27d55e457f 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' - ◉ 90e23f7e6c7c test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 + ◉ 27143b59c690 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' - ◉ b73abeebcf94 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 + ◉ 0e8aee02e242 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); // 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 2 operations and reparented 1 descendant operations. + 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("3a0226ca7b0f364650c64a8b8ffe3c9802c5db40d9cc19387a29a28826ccada0dd4ec6926e649c5eb3c8150ebd5cd8ffb3c2c8922445c5dc8ccf474cd83d0d9c") + Current operation: OperationId("1c88fada5b95d13ca136baa13c4b4aae1b79f3d453fe2f56539dbcd5d779642439314a15f685d6737eb414fffb3e53519f65f1d5cc5947a7821331137f4a91e2") Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) "###); insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log"]), @r###" - @ 3a0226ca7b0f test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 + @ 1c88fada5b95 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' - ◉ b73abeebcf94 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); // Abandon operation range. @@ -327,14 +349,14 @@ fn test_op_abandon_ancestors() { Abandoned 2 operations and reparented 1 descendant operations. "###); insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log"]), @r###" - @ 54d9d0dfc198 test-username@host.example.com 2001-02-03 04:05:16.000 +07:00 - 2001-02-03 04:05:16.000 +07:00 + @ 459e01910446 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' - ◉ 3a0226ca7b0f test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 + ◉ 1c88fada5b95 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' - ◉ b73abeebcf94 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); // Can't abandon the current operation. @@ -358,18 +380,18 @@ fn test_op_abandon_ancestors() { "###); insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["debug", "workingcopy", "--ignore-working-copy"]), @r###" - Current operation: OperationId("25cd03cd1029c1d22ea67cc11b89d357000bae5836f482be46d054220b1b08852713e32d0eec542bb275803214415ef8bb25b65a740fbc5c7b37094a311cac7c") + Current operation: OperationId("dbe91445f72184c82b2ad4c13063a20a764dd5a3bab42fc22652a0ca5d9f5a811d2b051560ca8b45c31f29e9d17ea408b224f761165b30f6df5f68ca74c90435") Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) "###); insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log"]), @r###" - @ 25cd03cd1029 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 54d9d0dfc198e3550d04acd89329f8b49765a94c4c85239ee44d6a602127ac658ed91933ad32211cc26e32b3ef2f384e9e0bab471327338fb1b3f04df7916cc2 + @ dbe91445f721 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 459e01910446fb8f6f6447112abc2227cb3b054b7831eca45bd527145719152931b1707d299f5e4e8484352e67d3622265a0baa58bfa01c459fd753f58791e0d │ args: jj undo - ◉ 3a0226ca7b0f test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 + ◉ 1c88fada5b95 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' - ◉ b73abeebcf94 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 @ 1970-01-01 00:00:00.000 +00:00 - 1970-01-01 00:00:00.000 +00:00 + "###); // Abandon empty range. @@ -378,8 +400,8 @@ fn test_op_abandon_ancestors() { Nothing changed. "###); insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["op", "log", "-l1"]), @r###" - @ 25cd03cd1029 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 54d9d0dfc198e3550d04acd89329f8b49765a94c4c85239ee44d6a602127ac658ed91933ad32211cc26e32b3ef2f384e9e0bab471327338fb1b3f04df7916cc2 + @ dbe91445f721 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 459e01910446fb8f6f6447112abc2227cb3b054b7831eca45bd527145719152931b1707d299f5e4e8484352e67d3622265a0baa58bfa01c459fd753f58791e0d │ args: jj undo "###); } @@ -404,12 +426,12 @@ fn test_op_abandon_without_updating_working_copy() { "###); insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["debug", "workingcopy", "--ignore-working-copy"]), @r###" - Current operation: OperationId("324622dffebde1f5b9c75347254d0481aa5f51931816782a8a5c0578fe74893fbf63ba7921e94f44058b1e1162e932648146136b4c13236cf2eccc42347e6dcf") + Current operation: OperationId("0229bff5a5244b0804cc677c77e318887b6d00422257de108158ef41fa5edcff38aa1683a3c00364835e0565b841eb41c5092300ec23a1aa5e393914d18fef32") Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) "###); insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["op", "log", "-l1", "--ignore-working-copy"]), @r###" - @ bc4e5d99e86e test-username@host.example.com 2001-02-03 04:05:10.000 +07:00 - 2001-02-03 04:05:10.000 +07:00 + @ 0173d6fbe6a2 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' "###); @@ -420,16 +442,16 @@ fn test_op_abandon_without_updating_working_copy() { 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 324622dffebd is not updated because it differs from the repo bc4e5d99e86e. + The working copy operation 0229bff5a524 is not updated because it differs from the repo 0173d6fbe6a2. "###); insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["debug", "workingcopy", "--ignore-working-copy"]), @r###" - Current operation: OperationId("324622dffebde1f5b9c75347254d0481aa5f51931816782a8a5c0578fe74893fbf63ba7921e94f44058b1e1162e932648146136b4c13236cf2eccc42347e6dcf") + Current operation: OperationId("0229bff5a5244b0804cc677c77e318887b6d00422257de108158ef41fa5edcff38aa1683a3c00364835e0565b841eb41c5092300ec23a1aa5e393914d18fef32") Current tree: Legacy(TreeId("4b825dc642cb6eb9a060e54bf8d69288fbee4904")) "###); insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["op", "log", "-l1", "--ignore-working-copy"]), @r###" - @ d7c3591bc1df test-username@host.example.com 2001-02-03 04:05:10.000 +07:00 - 2001-02-03 04:05:10.000 +07:00 + @ b66dc21d579b 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' "###); diff --git a/cli/tests/test_workspaces.rs b/cli/tests/test_workspaces.rs index 338694851..de50925e6 100644 --- a/cli/tests/test_workspaces.rs +++ b/cli/tests/test_workspaces.rs @@ -275,14 +275,14 @@ fn test_workspaces_conflicting_edits() { "###); let stderr = test_env.jj_cmd_failure(&secondary_path, &["st"]); insta::assert_snapshot!(stderr, @r###" - Error: The working copy is stale (not updated since operation 86c5150a3a9c). + Error: The working copy is stale (not updated since operation a07b009d6eba). Hint: Run `jj workspace update-stale` to update it. See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information. "###); // Same error on second run, and from another command let stderr = test_env.jj_cmd_failure(&secondary_path, &["log"]); insta::assert_snapshot!(stderr, @r###" - Error: The working copy is stale (not updated since operation 86c5150a3a9c). + Error: The working copy is stale (not updated since operation a07b009d6eba). Hint: Run `jj workspace update-stale` to update it. See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information. "###); @@ -362,7 +362,7 @@ fn test_workspaces_updated_by_other() { "###); let stderr = test_env.jj_cmd_failure(&secondary_path, &["st"]); insta::assert_snapshot!(stderr, @r###" - Error: The working copy is stale (not updated since operation 86c5150a3a9c). + Error: The working copy is stale (not updated since operation a07b009d6eba). Hint: Run `jj workspace update-stale` to update it. See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information. "###); @@ -409,6 +409,7 @@ fn test_workspaces_update_stale_noop() { insta::assert_snapshot!(stdout, @r###" @ add workspace 'default' ◉ initialize repo + ◉ "###); } @@ -543,7 +544,7 @@ fn test_workspaces_forget_multi_transaction() { // the op log should have multiple workspaces forgotten in a single tx let stdout = test_env.jj_cmd_success(&main_path, &["op", "log", "--limit", "1"]); insta::assert_snapshot!(stdout, @r###" - @ a2acfd91b649 test-username@host.example.com 2001-02-03 04:05:12.000 +07:00 - 2001-02-03 04:05:12.000 +07:00 + @ f96865b00a04 test-username@host.example.com 2001-02-03 04:05:12.000 +07:00 - 2001-02-03 04:05:12.000 +07:00 │ forget workspaces second, third │ args: jj workspace forget second third "###); diff --git a/lib/src/op_store.rs b/lib/src/op_store.rs index 1158bff45..be9e328df 100644 --- a/lib/src/op_store.rs +++ b/lib/src/op_store.rs @@ -23,7 +23,7 @@ use itertools::Itertools as _; use once_cell::sync::Lazy; use thiserror::Error; -use crate::backend::{CommitId, Timestamp}; +use crate::backend::{CommitId, MillisSinceEpoch, Timestamp}; use crate::content_hash::ContentHash; use crate::merge::Merge; use crate::object_id::{id_type, HexPrefix, ObjectId, PrefixResolution}; @@ -369,6 +369,28 @@ content_hash! { } } +impl Operation { + pub fn make_root(empty_view_id: ViewId) -> Operation { + let timestamp = Timestamp { + timestamp: MillisSinceEpoch(0), + tz_offset: 0, + }; + let metadata = OperationMetadata { + start_time: timestamp.clone(), + end_time: timestamp, + description: "".to_string(), + hostname: "".to_string(), + username: "".to_string(), + tags: HashMap::new(), + }; + Operation { + view_id: empty_view_id, + parents: vec![], + metadata, + } + } +} + content_hash! { #[derive(PartialEq, Eq, Clone, Debug)] pub struct OperationMetadata { @@ -410,6 +432,8 @@ pub type OpStoreResult = Result; pub trait OpStore: Send + Sync + Debug { fn name(&self) -> &str; + fn root_operation_id(&self) -> &OperationId; + fn read_view(&self, id: &ViewId) -> OpStoreResult; fn write_view(&self, contents: &View) -> OpStoreResult; diff --git a/lib/src/repo.rs b/lib/src/repo.rs index c9a742eac..e20d37051 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -177,7 +177,7 @@ impl ReadonlyRepo { let root_view_id = op_store.write_view(&root_view).unwrap(); let init_operation = op_store::Operation { view_id: root_view_id, - parents: vec![], + parents: vec![op_store.root_operation_id().clone()], metadata: operation_metadata, }; let init_operation_id = op_store.write_operation(&init_operation).unwrap(); diff --git a/lib/src/simple_op_store.rs b/lib/src/simple_op_store.rs index 15e1eeac7..e95af44e7 100644 --- a/lib/src/simple_op_store.rs +++ b/lib/src/simple_op_store.rs @@ -39,6 +39,7 @@ use crate::{dag_walk, git, op_store}; // BLAKE2b-512 hash length in bytes const OPERATION_ID_LENGTH: usize = 64; +const VIEW_ID_LENGTH: usize = 64; #[derive(Debug, Error)] #[error("Failed to read {kind} with ID {id}: {err}")] @@ -58,6 +59,8 @@ impl From for OpStoreError { #[derive(Debug)] pub struct SimpleOpStore { path: PathBuf, + empty_view_id: ViewId, + root_operation_id: OperationId, } impl SimpleOpStore { @@ -69,15 +72,15 @@ impl SimpleOpStore { pub fn init(store_path: &Path) -> Self { fs::create_dir(store_path.join("views")).unwrap(); fs::create_dir(store_path.join("operations")).unwrap(); - SimpleOpStore { - path: store_path.to_owned(), - } + Self::load(store_path) } /// Load an existing OpStore pub fn load(store_path: &Path) -> Self { SimpleOpStore { path: store_path.to_path_buf(), + empty_view_id: ViewId::from_bytes(&[0; VIEW_ID_LENGTH]), + root_operation_id: OperationId::from_bytes(&[0; OPERATION_ID_LENGTH]), } } @@ -95,7 +98,15 @@ impl OpStore for SimpleOpStore { Self::name() } + fn root_operation_id(&self) -> &OperationId { + &self.root_operation_id + } + fn read_view(&self, id: &ViewId) -> OpStoreResult { + if *id == self.empty_view_id { + return Ok(View::default()); + } + let path = self.view_path(id); let buf = fs::read(path).map_err(|err| io_to_read_error(err, id))?; @@ -125,6 +136,10 @@ impl OpStore for SimpleOpStore { } fn read_operation(&self, id: &OperationId) -> OpStoreResult { + if *id == self.root_operation_id { + return Ok(Operation::make_root(self.empty_view_id.clone())); + } + let path = self.operation_path(id); let buf = fs::read(path).map_err(|err| io_to_read_error(err, id))?; @@ -134,10 +149,17 @@ impl OpStore for SimpleOpStore { id: id.hex(), err, })?; - Ok(operation_from_proto(proto)) + let mut operation = operation_from_proto(proto); + if operation.parents.is_empty() { + // Repos created before we had the root operation will have an operation without + // parents. + operation.parents.push(self.root_operation_id.clone()); + } + Ok(operation) } fn write_operation(&self, operation: &Operation) -> OpStoreResult { + assert!(!operation.parents.is_empty()); let temp_file = NamedTempFile::new_in(&self.path).map_err(|err| io_to_write_error(err, "operation"))?; diff --git a/lib/tests/test_commit_concurrent.rs b/lib/tests/test_commit_concurrent.rs index f9fcd2637..800d92ab2 100644 --- a/lib/tests/test_commit_concurrent.rs +++ b/lib/tests/test_commit_concurrent.rs @@ -65,9 +65,9 @@ fn test_commit_parallel(backend: TestRepoBackend) { // the root commit assert_eq!(repo.view().heads().len(), num_threads + 1); - // One additional operation for initializing the repo, one for checking out the - // initial commit. - assert_eq!(count_non_merge_operations(&repo), num_threads + 2); + // One additional operation for the root commit, one for initializing the repo, + // one for checking out the initial commit. + assert_eq!(count_non_merge_operations(&repo), num_threads + 3); } #[test_case(TestRepoBackend::Local ; "local backend")] @@ -96,7 +96,7 @@ fn test_commit_parallel_instances(backend: TestRepoBackend) { let repo = load_repo_at_head(&settings, repo.repo_path()); assert_eq!(repo.view().heads().len(), num_threads + 1); - // One addition operation for initializing the repo, one for checking out the - // initial commit. - assert_eq!(count_non_merge_operations(&repo), num_threads + 2); + // One additional operation for the root commit, one for initializing the repo, + // one for checking out the initial commit. + assert_eq!(count_non_merge_operations(&repo), num_threads + 3); } diff --git a/lib/tests/test_operations.rs b/lib/tests/test_operations.rs index eb1a06e98..7fb282c7e 100644 --- a/lib/tests/test_operations.rs +++ b/lib/tests/test_operations.rs @@ -441,15 +441,15 @@ fn test_resolve_op_id() { repo = tx.commit(format!("transaction {i}")); operations.push(repo.operation().clone()); } - // "8" is ambiguous + // "1" is ambiguous insta::assert_debug_snapshot!(operations.iter().map(|op| op.id().hex()).collect_vec(), @r###" [ - "c304846fd5419130f961dc29a19e382ba1b48eeef92505da2f6be65c09a5321e36b3642bc8bf052cd47c2f0fa6cb103a837fc0d6b86a2ab5ef18c7ecfa6bec38", - "bffef6b286529cdebb9e48e209ea0eef96989bb23d8af4773847564d0b3b7961676ad9e23ddcd46774712198f5b3e28c2eb4fbba159ac640919b0c5f7d5a71d8", - "98d92c1e65617b594741968302d7225238f9f13e8126a6e12ee73f17def310a7b5be75a0d761399e2a9f3d13f066a274a4f40e6b9d588824da606a8888854f62", - "34d4b6ec40a425036a3e267cbe61ddc99582a1ec2601501d19a6a6c402d255aef5109ddc5bb6a79afaa17acc56a2e04bba8e69ef90a764aebb769f5038d24b64", - "89f50ea6a566fc5794164653511f35c40011321989a2499a6aec18780f0110cb5ce0fc0b248026cb32aa2b6a017a65a9472c497920933d13f68a95211c30bf2d", - "8913bd72461b92b2323b49a8789219f842942fd2040ddc078a4b7f731630f4bce8dabf181c3c55a3c54950023c04f6fc8f4174b92e9953230592a534222a5297", + "4ff2007de55a2f649f7ab0c98618e4126ef49f0d40a086c8e0a4612a0d5ab4992e1baf4b4fa0a2a224fab39fc5e5b200ac4cddf964db29c6be1379ab2b6d4572", + "cf1fd9ea3065ae077e13641278a213b371f98eb237334489c059e50e0a92a79cb058939f6fd2a89492f32dcecb14b6237f3e1fcf6fa454a9c21ef5425d78574c", + "5b29edf367805ca7e1dc9219ec25dae70ec853afbeed9a113dc2adba428f71eea944122097276fb664e2c8d6cc99a080d49ef58ce14356b5887d27b45952bfef", + "126647a88e08bc46f72db1eff34abe426cdd54e4e4c05b9b6773c288442bed2c5add304d4978e377694516ee89ed3436cec20ef2af4921247e6b6a90c3685a2a", + "5131849d86fe586dbbad3992d85b953b0ebe554f0bd7c42ee09628ab85db7ad909242901688a7d00590d326c8bdde21e8ed933f7ab4d6b7b16077e3d1c07b284", + "14073ae915621d1ec2358129440dc2a26e67e45995f46cf6199a6bb09b44d16a522451079a2480e4d2026d368759eacd1d5b3bc7a5915d9345d354b8e7ad46b4", ] "###); @@ -470,7 +470,7 @@ fn test_resolve_op_id() { ); // Ambiguous id assert_matches!( - op_walk::resolve_op_with_repo(&repo, "8"), + op_walk::resolve_op_with_repo(&repo, "1"), Err(OpsetEvaluationError::OpsetResolution( OpsetResolutionError::AmbiguousIdPrefix(_) )) @@ -521,9 +521,9 @@ fn test_resolve_op_parents_children() { op_walk::resolve_op_with_repo(repo, &format!("{op2_id_hex}--")).unwrap(), *operations[0] ); - // "{op2_id_hex}---" is the operation to initialize the repo. + // "{op2_id_hex}----" is the root operation assert_matches!( - op_walk::resolve_op_with_repo(repo, &format!("{op2_id_hex}----")), + op_walk::resolve_op_with_repo(repo, &format!("{op2_id_hex}-----")), Err(OpsetEvaluationError::OpsetResolution( OpsetResolutionError::EmptyOperations(_) ))