From 67500b8cdf62aaaf9762513253547843b0413213 Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Sat, 9 Mar 2024 08:55:52 -0800 Subject: [PATCH] git_backend: write trees involved in conflict in git commit header We haven't used custom Git commit headers for two main reasons: 1. I don't want commits created by jj to be different from any other commits. I don't want Git projects to get annoyed by such commit and reject them. 2. I've been concerned that tools don't know how to handle such headers, perhaps even resulting in crashes. The first argument doesn't apply to commits with conflicts because such commits would never be accepted by a project whether or not they use custom commit headers. The second argument is less relevant for conflicted commits because most tools will be confused by such commits anyway. Storing conflict information in commit headers means that we can transfer them via the regular Git wire protocol. We already include the tree objects nested inside the root-level tree, so they will also be transferred. So, let's start by writing the information redundantly to the commit header and to the existing storage. That way we can roll it back if we realize there's a problem with using commit headers. --- cli/tests/test_chmod_command.rs | 4 +- cli/tests/test_diffedit_command.rs | 6 +-- cli/tests/test_git_push.rs | 2 +- cli/tests/test_immutable_commits.rs | 36 ++++++++-------- cli/tests/test_obslog_command.rs | 14 +++--- cli/tests/test_repo_change_report.rs | 52 +++++++++++----------- cli/tests/test_resolve_command.rs | 12 +++--- cli/tests/test_restore_command.rs | 12 +++--- cli/tests/test_tree_level_conflicts.rs | 2 +- cli/tests/test_workspaces.rs | 4 +- lib/src/git_backend.rs | 60 ++++++++++++++++++++++---- 11 files changed, 124 insertions(+), 80 deletions(-) diff --git a/cli/tests/test_chmod_command.rs b/cli/tests/test_chmod_command.rs index ac4fbeb266..3da40b7bba 100644 --- a/cli/tests/test_chmod_command.rs +++ b/cli/tests/test_chmod_command.rs @@ -224,13 +224,13 @@ fn test_chmod_file_dir_deletion_conflicts() { insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" New conflicts appeared in these commits: - kmkuslsw 4cc432b5 file_deletion | (conflict) file_deletion + kmkuslsw b4c38719 file_deletion | (conflict) file_deletion To resolve the conflicts, start by updating to it: jj new kmkuslswpqwq Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: kmkuslsw 4cc432b5 file_deletion | (conflict) file_deletion + Working copy now at: kmkuslsw b4c38719 file_deletion | (conflict) file_deletion Parent commit : zsuskuln c51c9c55 file | file Parent commit : royxmykx 6b18b3c1 deletion | deletion Added 0 files, modified 1 files, removed 0 files diff --git a/cli/tests/test_diffedit_command.rs b/cli/tests/test_diffedit_command.rs index 0d69b01286..4d3cc96944 100644 --- a/cli/tests/test_diffedit_command.rs +++ b/cli/tests/test_diffedit_command.rs @@ -379,10 +379,10 @@ fn test_diffedit_merge() { let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit", "-r", "@-"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" - Created royxmykx 2b5202ae (conflict) merge + Created royxmykx b90654a0 (conflict) merge Rebased 1 descendant commits - Working copy now at: yqosqzyt 23b1fe1b (conflict) (empty) (no description set) - Parent commit : royxmykx 2b5202ae (conflict) merge + Working copy now at: yqosqzyt 1de824f2 (conflict) (empty) (no description set) + Parent commit : royxmykx b90654a0 (conflict) merge Added 0 files, modified 0 files, removed 1 files "###); let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s", "-r", "@-"]); diff --git a/cli/tests/test_git_push.rs b/cli/tests/test_git_push.rs index 612b3ce769..b4e00fd940 100644 --- a/cli/tests/test_git_push.rs +++ b/cli/tests/test_git_push.rs @@ -577,7 +577,7 @@ fn test_git_push_conflict() { test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "third"]); let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push", "--all"]); insta::assert_snapshot!(stderr, @r###" - Error: Won't push commit 1973d389875c since it has conflicts + Error: Won't push commit d9ca3146ade7 since it has conflicts "###); } diff --git a/cli/tests/test_immutable_commits.rs b/cli/tests/test_immutable_commits.rs index 96ca227ef1..0b016c6112 100644 --- a/cli/tests/test_immutable_commits.rs +++ b/cli/tests/test_immutable_commits.rs @@ -100,7 +100,7 @@ fn test_rewrite_immutable_commands() { insta::assert_snapshot!(stdout, @r###" @ yqosqzyt test.user@example.com 2001-02-03 04:05:13.000 +07:00 3f89addf │ (empty) (no description set) - │ ◉ mzvwutvl test.user@example.com 2001-02-03 04:05:11.000 +07:00 main 16ca9d80 conflict + │ ◉ mzvwutvl test.user@example.com 2001-02-03 04:05:11.000 +07:00 main 3d14df18 conflict ╭─┤ (empty) merge │ │ │ ~ @@ -113,61 +113,61 @@ fn test_rewrite_immutable_commands() { // abandon let stderr = test_env.jj_cmd_failure(&repo_path, &["abandon", "main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // chmod let stderr = test_env.jj_cmd_failure(&repo_path, &["chmod", "-r=main", "x", "file"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // describe let stderr = test_env.jj_cmd_failure(&repo_path, &["describe", "main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // diffedit let stderr = test_env.jj_cmd_failure(&repo_path, &["diffedit", "-r=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // edit let stderr = test_env.jj_cmd_failure(&repo_path, &["edit", "main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // move --from let stderr = test_env.jj_cmd_failure(&repo_path, &["move", "--from=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // move --to let stderr = test_env.jj_cmd_failure(&repo_path, &["move", "--to=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // new --insert-before let stderr = test_env.jj_cmd_failure(&repo_path, &["new", "--insert-before", "main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // new --insert-after parent_of_main let stderr = test_env.jj_cmd_failure(&repo_path, &["new", "--insert-after", "description(b)"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // rebase -s let stderr = test_env.jj_cmd_failure(&repo_path, &["rebase", "-s=main", "-d=@"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // rebase -b @@ -179,43 +179,43 @@ fn test_rewrite_immutable_commands() { // rebase -r let stderr = test_env.jj_cmd_failure(&repo_path, &["rebase", "-r=main", "-d=@"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // resolve let stderr = test_env.jj_cmd_failure(&repo_path, &["resolve", "-r=description(merge)", "file"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // restore -c let stderr = test_env.jj_cmd_failure(&repo_path, &["restore", "-c=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // restore --to let stderr = test_env.jj_cmd_failure(&repo_path, &["restore", "--to=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // split let stderr = test_env.jj_cmd_failure(&repo_path, &["split", "-r=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // squash let stderr = test_env.jj_cmd_failure(&repo_path, &["squash", "-r=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); // unsquash let stderr = test_env.jj_cmd_failure(&repo_path, &["unsquash", "-r=main"]); insta::assert_snapshot!(stderr, @r###" - Error: Commit 16ca9d800b08 is immutable + Error: Commit 3d14df18607e is immutable Hint: Configure the set of immutable commits via `revset-aliases.immutable_heads()`. "###); } diff --git a/cli/tests/test_obslog_command.rs b/cli/tests/test_obslog_command.rs index 5341637dea..af20ec0572 100644 --- a/cli/tests/test_obslog_command.rs +++ b/cli/tests/test_obslog_command.rs @@ -31,7 +31,7 @@ fn test_obslog_with_or_without_diff() { insta::assert_snapshot!(stdout, @r###" @ rlvkpnrz test.user@example.com 2001-02-03 04:05:10.000 +07:00 66b42ad3 │ my description - ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 5f4634a5 conflict + ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 ebc23d4b conflict │ my description ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 6fbba7bc │ my description @@ -44,11 +44,11 @@ fn test_obslog_with_or_without_diff() { insta::assert_snapshot!(stdout, @r###" @ rlvkpnrz test.user@example.com 2001-02-03 04:05:10.000 +07:00 66b42ad3 │ my description - ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 5f4634a5 conflict + ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 ebc23d4b conflict │ my description ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 6fbba7bc │ my description - ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:08.000 +07:00 eac0d0da + ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:08.000 +07:00 eac0d0da (empty) my description "###); @@ -66,7 +66,7 @@ fn test_obslog_with_or_without_diff() { │ 5 : foo │ 6 : bar │ 7 : >>>>>>> - ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 5f4634a5 conflict + ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 ebc23d4b conflict │ my description ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 6fbba7bc │ my description @@ -84,7 +84,7 @@ fn test_obslog_with_or_without_diff() { insta::assert_snapshot!(stdout, @r###" @ rlvkpnrz test.user@example.com 2001-02-03 04:05:10.000 +07:00 66b42ad3 │ my description - ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 5f4634a5 conflict + ◉ rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 ebc23d4b conflict │ my description "###); @@ -93,7 +93,7 @@ fn test_obslog_with_or_without_diff() { insta::assert_snapshot!(stdout, @r###" rlvkpnrz test.user@example.com 2001-02-03 04:05:10.000 +07:00 66b42ad3 my description - rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 5f4634a5 conflict + rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 ebc23d4b conflict my description rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 6fbba7bc my description @@ -119,7 +119,7 @@ fn test_obslog_with_or_without_diff() { -bar ->>>>>>> +resolved - rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 5f4634a5 conflict + rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 ebc23d4b conflict my description rlvkpnrz hidden test.user@example.com 2001-02-03 04:05:09.000 +07:00 6fbba7bc my description diff --git a/cli/tests/test_repo_change_report.rs b/cli/tests/test_repo_change_report.rs index d8ea91bd8b..54a566e748 100644 --- a/cli/tests/test_repo_change_report.rs +++ b/cli/tests/test_repo_change_report.rs @@ -33,15 +33,15 @@ fn test_report_conflicts() { insta::assert_snapshot!(stderr, @r###" Rebased 3 commits New conflicts appeared in these commits: - kkmpptxz a2593769 (conflict) C - rlvkpnrz 727244df (conflict) B + kkmpptxz 9baab11e (conflict) C + rlvkpnrz de73196a (conflict) B To resolve the conflicts, start by updating to the first one: jj new rlvkpnrzqnoo Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: zsuskuln 30928080 (conflict) (empty) (no description set) - Parent commit : kkmpptxz a2593769 (conflict) C + Working copy now at: zsuskuln 7dc9bf15 (conflict) (empty) (no description set) + Parent commit : kkmpptxz 9baab11e (conflict) C Added 0 files, modified 1 files, removed 0 files "###); @@ -50,8 +50,8 @@ fn test_report_conflicts() { insta::assert_snapshot!(stderr, @r###" Rebased 3 commits Existing conflicts were resolved or abandoned from these commits: - kkmpptxz hidden a2593769 (conflict) C - rlvkpnrz hidden 727244df (conflict) B + kkmpptxz hidden 9baab11e (conflict) C + rlvkpnrz hidden de73196a (conflict) B Working copy now at: zsuskuln 355a2e34 (empty) (no description set) Parent commit : kkmpptxz ed071401 C Added 0 files, modified 1 files, removed 0 files @@ -64,16 +64,16 @@ fn test_report_conflicts() { insta::assert_snapshot!(stderr, @r###" Also rebased 2 descendant commits onto parent of rebased commit New conflicts appeared in these commits: - rlvkpnrz 9df65f08 (conflict) B - kkmpptxz 7530822d (conflict) C + rlvkpnrz e93270ab (conflict) B + kkmpptxz 4f0eeaa6 (conflict) C To resolve the conflicts, start by updating to one of the first ones: jj new rlvkpnrzqnoo jj new kkmpptxzrspx Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: zsuskuln 203be58b (conflict) (empty) (no description set) - Parent commit : kkmpptxz 7530822d (conflict) C + Working copy now at: zsuskuln 83074dac (conflict) (empty) (no description set) + Parent commit : kkmpptxz 4f0eeaa6 (conflict) C Added 0 files, modified 1 files, removed 0 files "###); @@ -81,8 +81,8 @@ fn test_report_conflicts() { let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["new", "rlvkpnrzqnoo"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" - Working copy now at: vruxwmqv 406f84d0 (conflict) (empty) (no description set) - Parent commit : rlvkpnrz 9df65f08 (conflict) B + Working copy now at: vruxwmqv 2ec0b4c3 (conflict) (empty) (no description set) + Parent commit : rlvkpnrz e93270ab (conflict) B Added 0 files, modified 1 files, removed 0 files "###); std::fs::write(repo_path.join("file"), "resolved\n").unwrap(); @@ -90,7 +90,7 @@ fn test_report_conflicts() { insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Existing conflicts were resolved or abandoned from these commits: - rlvkpnrz hidden 9df65f08 (conflict) B + rlvkpnrz hidden e93270ab (conflict) B Working copy now at: yostqsxw 8e160bc4 (empty) (no description set) Parent commit : rlvkpnrz c5319490 B "###); @@ -118,16 +118,16 @@ fn test_report_conflicts_with_divergent_commits() { Concurrent modification detected, resolving automatically. Rebased 3 commits New conflicts appeared in these commits: - zsuskuln?? 76c40a95 (conflict) C3 - zsuskuln?? e92329f2 (conflict) C2 - kkmpptxz aed319ec (conflict) B + zsuskuln?? 94be9a4c (conflict) C3 + zsuskuln?? cdae4322 (conflict) C2 + kkmpptxz b76d6a88 (conflict) B To resolve the conflicts, start by updating to the first one: jj new kkmpptxzrspx Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: zsuskuln?? e92329f2 (conflict) C2 - Parent commit : kkmpptxz aed319ec (conflict) B + Working copy now at: zsuskuln?? cdae4322 (conflict) C2 + Parent commit : kkmpptxz b76d6a88 (conflict) B Added 0 files, modified 1 files, removed 0 files "###); @@ -136,9 +136,9 @@ fn test_report_conflicts_with_divergent_commits() { insta::assert_snapshot!(stderr, @r###" Rebased 3 commits Existing conflicts were resolved or abandoned from these commits: - zsuskuln hidden 76c40a95 (conflict) C3 - zsuskuln hidden e92329f2 (conflict) C2 - kkmpptxz hidden aed319ec (conflict) B + zsuskuln hidden 94be9a4c (conflict) C3 + zsuskuln hidden cdae4322 (conflict) C2 + kkmpptxz hidden b76d6a88 (conflict) B Working copy now at: zsuskuln?? 9c33e9a9 C2 Parent commit : kkmpptxz 9ce42c2a B Added 0 files, modified 1 files, removed 0 files @@ -151,13 +151,13 @@ fn test_report_conflicts_with_divergent_commits() { insta::assert_snapshot!(stderr, @r###" Rebased 1 commits New conflicts appeared in these commits: - zsuskuln?? 0d6cb6b7 (conflict) C2 + zsuskuln?? 33752e7e (conflict) C2 To resolve the conflicts, start by updating to it: jj new zsuskulnrvyr Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: zsuskuln?? 0d6cb6b7 (conflict) C2 + Working copy now at: zsuskuln?? 33752e7e (conflict) C2 Parent commit : zzzzzzzz 00000000 (empty) (no description set) Added 0 files, modified 1 files, removed 0 files "###); @@ -168,7 +168,7 @@ fn test_report_conflicts_with_divergent_commits() { insta::assert_snapshot!(stderr, @r###" Rebased 1 commits New conflicts appeared in these commits: - zsuskuln?? 9652a362 (conflict) C3 + zsuskuln?? 37bb9c2f (conflict) C3 To resolve the conflicts, start by updating to it: jj new zsuskulnrvyr Then use `jj resolve`, or edit the conflict markers in the file directly. @@ -184,7 +184,7 @@ fn test_report_conflicts_with_divergent_commits() { insta::assert_snapshot!(stderr, @r###" Rebased 1 commits Existing conflicts were resolved or abandoned from these commits: - zsuskuln hidden 0d6cb6b7 (conflict) C2 + zsuskuln hidden 33752e7e (conflict) C2 Working copy now at: zsuskuln?? 24f79296 C2 Parent commit : kkmpptxz 9ce42c2a B Added 0 files, modified 1 files, removed 0 files @@ -198,6 +198,6 @@ fn test_report_conflicts_with_divergent_commits() { insta::assert_snapshot!(stderr, @r###" Rebased 1 commits Existing conflicts were resolved or abandoned from these commits: - zsuskuln hidden 9652a362 (conflict) C3 + zsuskuln hidden 37bb9c2f (conflict) C3 "###); } diff --git a/cli/tests/test_resolve_command.rs b/cli/tests/test_resolve_command.rs index 232f16c1ed..a1ff2954cf 100644 --- a/cli/tests/test_resolve_command.rs +++ b/cli/tests/test_resolve_command.rs @@ -220,13 +220,13 @@ conflict insta::assert_snapshot!(stderr, @r###" Resolving conflicts in: file New conflicts appeared in these commits: - vruxwmqv 23991847 conflict | (conflict) conflict + vruxwmqv 8144e92d conflict | (conflict) conflict To resolve the conflicts, start by updating to it: jj new vruxwmqvtpmx Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: vruxwmqv 23991847 conflict | (conflict) conflict + Working copy now at: vruxwmqv 8144e92d conflict | (conflict) conflict Parent commit : zsuskuln aa493daf a | a Parent commit : royxmykx db6a4daf b | b Added 0 files, modified 1 files, removed 0 files @@ -690,13 +690,13 @@ fn test_multiple_conflicts() { insta::assert_snapshot!(stderr, @r###" Resolving conflicts in: another_file New conflicts appeared in these commits: - vruxwmqv c3c25bce conflict | (conflict) conflict + vruxwmqv 1e22a8e4 conflict | (conflict) conflict To resolve the conflicts, start by updating to it: jj new vruxwmqvtpmx Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: vruxwmqv c3c25bce conflict | (conflict) conflict + Working copy now at: vruxwmqv 1e22a8e4 conflict | (conflict) conflict Parent commit : zsuskuln de7553ef a | a Parent commit : royxmykx f68bc2f0 b | b Added 0 files, modified 1 files, removed 0 files @@ -727,13 +727,13 @@ fn test_multiple_conflicts() { insta::assert_snapshot!(stderr, @r###" Resolving conflicts in: another_file New conflicts appeared in these commits: - vruxwmqv fd3874cd conflict | (conflict) conflict + vruxwmqv 3c438f88 conflict | (conflict) conflict To resolve the conflicts, start by updating to it: jj new vruxwmqvtpmx Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: vruxwmqv fd3874cd conflict | (conflict) conflict + Working copy now at: vruxwmqv 3c438f88 conflict | (conflict) conflict Parent commit : zsuskuln de7553ef a | a Parent commit : royxmykx f68bc2f0 b | b Added 0 files, modified 1 files, removed 0 files diff --git a/cli/tests/test_restore_command.rs b/cli/tests/test_restore_command.rs index c3d8792ed3..0c8f1a2e3e 100644 --- a/cli/tests/test_restore_command.rs +++ b/cli/tests/test_restore_command.rs @@ -62,13 +62,13 @@ fn test_restore() { Created rlvkpnrz e25100af (empty) (no description set) Rebased 1 descendant commits New conflicts appeared in these commits: - kkmpptxz e301deb3 (conflict) (no description set) + kkmpptxz 761deaef (conflict) (no description set) To resolve the conflicts, start by updating to it: jj new kkmpptxzrspx Then use `jj resolve`, or edit the conflict markers in the file directly. Once the conflicts are resolved, you may want inspect the result with `jj diff`. Then run `jj squash` to move the resolution into the conflicted commit. - Working copy now at: kkmpptxz e301deb3 (conflict) (no description set) + Working copy now at: kkmpptxz 761deaef (conflict) (no description set) Parent commit : rlvkpnrz e25100af (empty) (no description set) Added 0 files, modified 1 files, removed 0 files "###); @@ -197,8 +197,8 @@ fn test_restore_conflicted_merge() { let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["restore", "file"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" - Created vruxwmqv b2c9c888 conflict | (conflict) (empty) conflict - Working copy now at: vruxwmqv b2c9c888 conflict | (conflict) (empty) conflict + Created vruxwmqv 0817af7e conflict | (conflict) (empty) conflict + Working copy now at: vruxwmqv 0817af7e conflict | (conflict) (empty) conflict Parent commit : zsuskuln aa493daf a | a Parent commit : royxmykx db6a4daf b | b Added 0 files, modified 1 files, removed 0 files @@ -236,8 +236,8 @@ fn test_restore_conflicted_merge() { let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["restore"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" - Created vruxwmqv 4fc10820 conflict | (conflict) (empty) conflict - Working copy now at: vruxwmqv 4fc10820 conflict | (conflict) (empty) conflict + Created vruxwmqv da925083 conflict | (conflict) (empty) conflict + Working copy now at: vruxwmqv da925083 conflict | (conflict) (empty) conflict Parent commit : zsuskuln aa493daf a | a Parent commit : royxmykx db6a4daf b | b Added 0 files, modified 1 files, removed 0 files diff --git a/cli/tests/test_tree_level_conflicts.rs b/cli/tests/test_tree_level_conflicts.rs index 8480a7099f..2b3680394d 100644 --- a/cli/tests/test_tree_level_conflicts.rs +++ b/cli/tests/test_tree_level_conflicts.rs @@ -56,7 +56,7 @@ fn test_enable_tree_level_conflicts() { // non-empty let stdout = test_env.jj_cmd_success(&repo_path, &["log"]); insta::assert_snapshot!(stdout, @r###" - @ mzvwutvl test.user@example.com 2001-02-03 04:05:13.000 +07:00 54c562fa conflict + @ mzvwutvl test.user@example.com 2001-02-03 04:05:13.000 +07:00 51f1748d conflict │ (no description set) ◉ zsuskuln test.user@example.com 2001-02-03 04:05:10.000 +07:00 5100e4e1 conflict ├─╮ (empty) merge diff --git a/cli/tests/test_workspaces.rs b/cli/tests/test_workspaces.rs index 428cc8e596..83fd467609 100644 --- a/cli/tests/test_workspaces.rs +++ b/cli/tests/test_workspaces.rs @@ -321,7 +321,7 @@ fn test_workspaces_conflicting_edits() { "###); insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r###" - ◉ a3c96849ef9f124cbfc2416dc13bf17309d5020a (divergent) + ◉ b0b43f24d501eab73ea245c115cdc571541e4a51 (divergent) │ ◉ fe8f41ed01d693b2d4365cd89e42ad9c531a939b default@ ├─╯ │ @ a1896a17282f19089a5cec44358d6609910e0513 secondary@ (divergent) @@ -333,7 +333,7 @@ fn test_workspaces_conflicting_edits() { let stdout = get_log_output(&test_env, &secondary_path); assert!(!stdout.starts_with("The working copy is stale")); insta::assert_snapshot!(stdout, @r###" - ◉ a3c96849ef9f124cbfc2416dc13bf17309d5020a (divergent) + ◉ b0b43f24d501eab73ea245c115cdc571541e4a51 (divergent) │ ◉ fe8f41ed01d693b2d4365cd89e42ad9c531a939b default@ ├─╯ │ @ a1896a17282f19089a5cec44358d6609910e0513 secondary@ (divergent) diff --git a/lib/src/git_backend.rs b/lib/src/git_backend.rs index 2c00ad6eeb..d9dc9a89da 100644 --- a/lib/src/git_backend.rs +++ b/lib/src/git_backend.rs @@ -25,7 +25,8 @@ use std::time::SystemTime; use std::{fs, io, str}; use async_trait::async_trait; -use gix::objs::{CommitRefIter, WriteTo}; +use gix::bstr::BString; +use gix::objs::{CommitRef, CommitRefIter, WriteTo}; use itertools::Itertools; use prost::Message; use smallvec::SmallVec; @@ -54,6 +55,8 @@ const CHANGE_ID_LENGTH: usize = 16; const NO_GC_REF_NAMESPACE: &str = "refs/jj/keep/"; const CONFLICT_SUFFIX: &str = ".jjconflict"; +const JJ_TREES_COMMIT_HEADER: &[u8] = b"jj:trees"; + #[derive(Debug, Error)] pub enum GitBackendInitError { #[error("Failed to initialize git repository")] @@ -420,6 +423,27 @@ fn gix_open_opts_from_settings(settings: &UserSettings) -> gix::open::Options { .open_path_as_is(true) } +/// Reads the `jj:trees` header from the commit. +fn root_tree_from_header(git_commit: &CommitRef) -> Result, ()> { + for (key, value) in &git_commit.extra_headers { + if *key == JJ_TREES_COMMIT_HEADER { + let mut tree_ids = SmallVec::new(); + for hex in str::from_utf8(value.as_ref()).or(Err(()))?.split(' ') { + let tree_id = TreeId::try_from_hex(hex).or(Err(()))?; + if tree_id.as_bytes().len() != HASH_LENGTH { + return Err(()); + } + tree_ids.push(tree_id); + } + if tree_ids.len() % 2 == 0 { + return Err(()); + } + return Ok(Some(MergedTreeId::Merge(Merge::from_vec(tree_ids)))); + } + } + Ok(None) +} + fn commit_from_git_without_root_parent( id: &CommitId, git_object: &gix::Object, @@ -449,11 +473,15 @@ fn commit_from_git_without_root_parent( let tree_id = TreeId::from_bytes(commit.tree().as_bytes()); // If this commit is a conflict, we'll update the root tree later, when we read // the extra metadata. - let root_tree = if uses_tree_conflict_format { - MergedTreeId::resolved(tree_id) - } else { - MergedTreeId::Legacy(tree_id) - }; + let root_tree = root_tree_from_header(&commit) + .map_err(|()| to_read_object_err("Invalid jj:trees header", id))?; + let root_tree = root_tree.unwrap_or_else(|| { + if uses_tree_conflict_format { + MergedTreeId::resolved(tree_id) + } else { + MergedTreeId::Legacy(tree_id) + } + }); // Use lossy conversion as commit message with "mojibake" is still better than // nothing. // TODO: what should we do with commit.encoding? @@ -571,7 +599,13 @@ fn deserialize_extras(commit: &mut Commit, bytes: &[u8]) { .iter() .map(|id_bytes| TreeId::from_bytes(id_bytes)) .collect(); - commit.root_tree = MergedTreeId::Merge(merge_builder.build()); + let merge = merge_builder.build(); + // Check that the trees from the extras match the one we found in the jj:trees + // header + if let MergedTreeId::Merge(existing_merge) = &commit.root_tree { + assert!(existing_merge.is_resolved() || *existing_merge == merge); + } + commit.root_tree = MergedTreeId::Merge(merge); } else { // uses_tree_conflict_format was set but there was no root_tree override in the // proto, which means we should just promote the tree id from the @@ -1097,6 +1131,16 @@ impl Backend for GitBackend { parents.push(validate_git_object_id(parent_id)?); } } + let mut extra_headers = vec![]; + if let MergedTreeId::Merge(tree_ids) = &contents.root_tree { + if !tree_ids.is_resolved() { + let value = tree_ids.iter().map(|id| id.hex()).join(" ").into_bytes(); + extra_headers.push(( + BString::new(JJ_TREES_COMMIT_HEADER.to_vec()), + BString::new(value), + )); + } + } let extras = serialize_extras(&contents); // If two writers write commits of the same id with different metadata, they @@ -1114,7 +1158,7 @@ impl Backend for GitBackend { committer: committer.into(), encoding: None, parents: parents.clone(), - extra_headers: Default::default(), + extra_headers: extra_headers.clone(), }; if let Some(sign) = &mut sign_with {