jj/cli/tests/test_absorb_command.rs
Yuya Nishihara 39f481f2da absorb: add basic support for file deletion
This works if the file was added and wasn't modified within the destination
range.

Closes #6140
2025-04-17 18:10:23 +00:00

952 lines
29 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2024 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 crate::common::CommandOutput;
use crate::common::TestEnvironment;
use crate::common::TestWorkDir;
#[test]
fn test_absorb_simple() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m0"]).success();
work_dir.write_file("file1", "");
work_dir.run_jj(["new", "-m1"]).success();
work_dir.write_file("file1", "1a\n1b\n");
work_dir.run_jj(["new", "-m2"]).success();
work_dir.write_file("file1", "1a\n1b\n2a\n2b\n");
// Empty commit
work_dir.run_jj(["new"]).success();
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
// Insert first and last lines
work_dir.write_file("file1", "1X\n1a\n1b\n2a\n2b\n2Z\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 2 revisions:
zsuskuln 3027ca7a 2
kkmpptxz d0f1e8dd 1
Working copy (@) now at: yqosqzyt 277bed24 (empty) (no description set)
Parent commit (@-) : zsuskuln 3027ca7a 2
[EOF]
");
// Modify middle line in hunk
work_dir.write_file("file1", "1X\n1A\n1b\n2a\n2b\n2Z\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
kkmpptxz d366d92c 1
Rebased 1 descendant commits.
Working copy (@) now at: vruxwmqv 32eb72fe (empty) (no description set)
Parent commit (@-) : zsuskuln 5bf0bc06 2
[EOF]
");
// Remove middle line from hunk
work_dir.write_file("file1", "1X\n1A\n1b\n2a\n2Z\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
zsuskuln 6e2c4777 2
Working copy (@) now at: yostqsxw 4a48490c (empty) (no description set)
Parent commit (@-) : zsuskuln 6e2c4777 2
[EOF]
");
// Insert ambiguous line in between
work_dir.write_file("file1", "1X\n1A\n1b\nY\n2a\n2Z\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ yostqsxw 80965bcc (no description set)
│ diff --git a/file1 b/file1
│ index 8653ca354d..88eb438902 100644
│ --- a/file1
│ +++ b/file1
│ @@ -1,5 +1,6 @@
│ 1X
│ 1A
│ 1b
│ +Y
│ 2a
│ 2Z
○ zsuskuln 6e2c4777 2
│ diff --git a/file1 b/file1
│ index ed237b5112..8653ca354d 100644
│ --- a/file1
│ +++ b/file1
│ @@ -1,3 +1,5 @@
│ 1X
│ 1A
│ 1b
│ +2a
│ +2Z
○ kkmpptxz d366d92c 1
│ diff --git a/file1 b/file1
│ index e69de29bb2..ed237b5112 100644
│ --- a/file1
│ +++ b/file1
│ @@ -0,0 +1,3 @@
│ +1X
│ +1A
│ +1b
○ qpvuntsm 1a4edb91 0
│ diff --git a/file1 b/file1
~ new file mode 100644
index 0000000000..e69de29bb2
[EOF]
");
insta::assert_snapshot!(get_evolog(&work_dir, "description(1)"), @r"
○ kkmpptxz d366d92c 1
├─╮
│ ○ yqosqzyt hidden c506fbc7 (no description set)
│ ○ yqosqzyt hidden 277bed24 (empty) (no description set)
○ kkmpptxz hidden d0f1e8dd 1
├─╮
│ ○ mzvwutvl hidden 8935ee61 (no description set)
│ ○ mzvwutvl hidden 2bc3d2ce (empty) (no description set)
○ kkmpptxz hidden ee76d790 1
○ kkmpptxz hidden 677e62d5 (empty) 1
[EOF]
");
insta::assert_snapshot!(get_evolog(&work_dir, "description(2)"), @r"
○ zsuskuln 6e2c4777 2
├─╮
│ ○ vruxwmqv hidden 7b1da5cd (no description set)
│ ○ vruxwmqv hidden 32eb72fe (empty) (no description set)
○ zsuskuln hidden 5bf0bc06 2
○ zsuskuln hidden 3027ca7a 2
├─╮
│ ○ mzvwutvl hidden 8935ee61 (no description set)
│ ○ mzvwutvl hidden 2bc3d2ce (empty) (no description set)
○ zsuskuln hidden cca09b4d 2
○ zsuskuln hidden 7b092471 (empty) 2
[EOF]
");
}
#[test]
fn test_absorb_replace_single_line_hunk() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m1"]).success();
work_dir.write_file("file1", "1a\n");
work_dir.run_jj(["new", "-m2"]).success();
work_dir.write_file("file1", "2a\n1a\n2b\n");
// Replace single-line hunk, which produces a conflict right now. If our
// merge logic were based on interleaved delta, the hunk would be applied
// cleanly.
work_dir.run_jj(["new"]).success();
work_dir.write_file("file1", "2a\n1A\n2b\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
qpvuntsm 7e885236 (conflict) 1
Rebased 1 descendant commits.
Working copy (@) now at: mzvwutvl e9c3b95b (empty) (no description set)
Parent commit (@-) : kkmpptxz 7c36845c 2
New conflicts appeared in 1 commits:
qpvuntsm 7e885236 (conflict) 1
Hint: To resolve the conflicts, start by updating to it:
jj new qpvuntsm
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ mzvwutvl e9c3b95b (empty) (no description set)
○ kkmpptxz 7c36845c 2
│ diff --git a/file1 b/file1
│ index 0000000000..2f87e8e465 100644
│ --- a/file1
│ +++ b/file1
│ @@ -1,10 +1,3 @@
│ -<<<<<<< Conflict 1 of 1
│ -%%%%%%% Changes from base to side #1
│ --2a
│ - 1a
│ --2b
│ -+++++++ Contents of side #2
│ 2a
│ 1A
│ 2b
│ ->>>>>>> Conflict 1 of 1 ends
× qpvuntsm 7e885236 (conflict) 1
│ diff --git a/file1 b/file1
~ new file mode 100644
index 0000000000..0000000000
--- /dev/null
+++ b/file1
@@ -0,0 +1,10 @@
+<<<<<<< Conflict 1 of 1
+%%%%%%% Changes from base to side #1
+-2a
+ 1a
+-2b
++++++++ Contents of side #2
+2a
+1A
+2b
+>>>>>>> Conflict 1 of 1 ends
[EOF]
");
}
#[test]
fn test_absorb_merge() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m0"]).success();
work_dir.write_file("file1", "0a\n");
work_dir.run_jj(["new", "-m1"]).success();
work_dir.write_file("file1", "1a\n1b\n0a\n");
work_dir.run_jj(["new", "-m2", "description(0)"]).success();
work_dir.write_file("file1", "0a\n2a\n2b\n");
let output = work_dir.run_jj(["new", "-m3", "description(1)", "description(2)"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Working copy (@) now at: mzvwutvl 08898161 (empty) 3
Parent commit (@-) : kkmpptxz 7e9df299 1
Parent commit (@-) : zsuskuln baf056cf 2
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
// Modify first and last lines, absorb from merge
work_dir.write_file("file1", "1A\n1b\n0a\n2a\n2B\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 2 revisions:
zsuskuln 71d1ee56 2
kkmpptxz 4d379399 1
Rebased 1 descendant commits.
Working copy (@) now at: mzvwutvl 9db19b54 (empty) 3
Parent commit (@-) : kkmpptxz 4d379399 1
Parent commit (@-) : zsuskuln 71d1ee56 2
[EOF]
");
// Add hunk to merge revision
work_dir.write_file("file2", "3a\n");
// Absorb into merge
work_dir.run_jj(["new"]).success();
work_dir.write_file("file2", "3A\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
mzvwutvl e93c0210 3
Working copy (@) now at: vruxwmqv 1b10dfa4 (empty) (no description set)
Parent commit (@-) : mzvwutvl e93c0210 3
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ vruxwmqv 1b10dfa4 (empty) (no description set)
○ mzvwutvl e93c0210 3
├─╮ diff --git a/file2 b/file2
│ │ new file mode 100644
│ │ index 0000000000..44442d2d7b
│ │ --- /dev/null
│ │ +++ b/file2
│ │ @@ -0,0 +1,1 @@
│ │ +3A
│ ○ zsuskuln 71d1ee56 2
│ │ diff --git a/file1 b/file1
│ │ index eb6e8821f1..4907935b9f 100644
│ │ --- a/file1
│ │ +++ b/file1
│ │ @@ -1,1 +1,3 @@
│ │ 0a
│ │ +2a
│ │ +2B
○ │ kkmpptxz 4d379399 1
├─╯ diff --git a/file1 b/file1
│ index eb6e8821f1..902dd8ef13 100644
│ --- a/file1
│ +++ b/file1
│ @@ -1,1 +1,3 @@
│ +1A
│ +1b
│ 0a
○ qpvuntsm 3777b700 0
│ diff --git a/file1 b/file1
~ new file mode 100644
index 0000000000..eb6e8821f1
--- /dev/null
+++ b/file1
@@ -0,0 +1,1 @@
+0a
[EOF]
");
}
#[test]
fn test_absorb_discardable_merge_with_descendant() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m0"]).success();
work_dir.write_file("file1", "0a\n");
work_dir.run_jj(["new", "-m1"]).success();
work_dir.write_file("file1", "1a\n1b\n0a\n");
work_dir.run_jj(["new", "-m2", "description(0)"]).success();
work_dir.write_file("file1", "0a\n2a\n2b\n");
let output = work_dir.run_jj(["new", "description(1)", "description(2)"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Working copy (@) now at: mzvwutvl f59b2364 (empty) (no description set)
Parent commit (@-) : kkmpptxz 7e9df299 1
Parent commit (@-) : zsuskuln baf056cf 2
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
// Modify first and last lines in the merge commit
work_dir.write_file("file1", "1A\n1b\n0a\n2a\n2B\n");
// Add new commit on top
work_dir.run_jj(["new", "-m3"]).success();
work_dir.write_file("file2", "3a\n");
// Then absorb the merge commit
let output = work_dir.run_jj(["absorb", "--from=@-"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 2 revisions:
zsuskuln 02668cf6 2
kkmpptxz fcabe394 1
Rebased 1 descendant commits.
Working copy (@) now at: royxmykx f04f1247 3
Parent commit (@-) : kkmpptxz fcabe394 1
Parent commit (@-) : zsuskuln 02668cf6 2
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ royxmykx f04f1247 3
├─╮ diff --git a/file2 b/file2
│ │ new file mode 100644
│ │ index 0000000000..31cd755d20
│ │ --- /dev/null
│ │ +++ b/file2
│ │ @@ -0,0 +1,1 @@
│ │ +3a
│ ○ zsuskuln 02668cf6 2
│ │ diff --git a/file1 b/file1
│ │ index eb6e8821f1..4907935b9f 100644
│ │ --- a/file1
│ │ +++ b/file1
│ │ @@ -1,1 +1,3 @@
│ │ 0a
│ │ +2a
│ │ +2B
○ │ kkmpptxz fcabe394 1
├─╯ diff --git a/file1 b/file1
│ index eb6e8821f1..902dd8ef13 100644
│ --- a/file1
│ +++ b/file1
│ @@ -1,1 +1,3 @@
│ +1A
│ +1b
│ 0a
○ qpvuntsm 3777b700 0
│ diff --git a/file1 b/file1
~ new file mode 100644
index 0000000000..eb6e8821f1
--- /dev/null
+++ b/file1
@@ -0,0 +1,1 @@
+0a
[EOF]
");
}
#[test]
fn test_absorb_conflict() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m1"]).success();
work_dir.write_file("file1", "1a\n1b\n");
work_dir.run_jj(["new", "root()"]).success();
work_dir.write_file("file1", "2a\n2b\n");
let output = work_dir.run_jj(["rebase", "-r@", "-ddescription(1)"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Rebased 1 commits onto destination
Working copy (@) now at: kkmpptxz 74405a07 (conflict) (no description set)
Parent commit (@-) : qpvuntsm 3619e4e5 1
Added 0 files, modified 1 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
file1 2-sided conflict
New conflicts appeared in 1 commits:
kkmpptxz 74405a07 (conflict) (no description set)
Hint: To resolve the conflicts, start by updating to it:
jj new kkmpptxz
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
[EOF]
");
let conflict_content = work_dir.read_file("file1");
insta::assert_snapshot!(conflict_content, @r"
<<<<<<< Conflict 1 of 1
%%%%%%% Changes from base to side #1
+1a
+1b
+++++++ Contents of side #2
2a
2b
>>>>>>> Conflict 1 of 1 ends
");
// Cannot absorb from conflict
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Skipping file1: Is a conflict
Nothing changed.
[EOF]
");
// Cannot absorb from resolved conflict
work_dir.run_jj(["new"]).success();
work_dir.write_file("file1", "1A\n1b\n2a\n2B\n");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Skipping file1: Is a conflict
Nothing changed.
[EOF]
");
}
#[test]
fn test_absorb_deleted_file() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m1"]).success();
work_dir.write_file("file1", "1a\n");
work_dir.write_file("file2", "1a\n");
work_dir.write_file("file3", "");
work_dir.run_jj(["new"]).success();
work_dir.remove_file("file1");
work_dir.write_file("file2", ""); // emptied
work_dir.remove_file("file3"); // no content change
// Since the destinations are chosen based on content diffs, file3 cannot be
// absorbed.
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
qpvuntsm f3c5cd48 1
Rebased 1 descendant commits.
Working copy (@) now at: kkmpptxz 691fa544 (no description set)
Parent commit (@-) : qpvuntsm f3c5cd48 1
Remaining changes:
D file3
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ kkmpptxz 691fa544 (no description set)
│ diff --git a/file3 b/file3
│ deleted file mode 100644
│ index e69de29bb2..0000000000
○ qpvuntsm f3c5cd48 1
│ diff --git a/file2 b/file2
~ new file mode 100644
index 0000000000..e69de29bb2
diff --git a/file3 b/file3
new file mode 100644
index 0000000000..e69de29bb2
[EOF]
");
}
#[test]
fn test_absorb_deleted_file_with_multiple_hunks() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m1"]).success();
work_dir.write_file("file1", "1a\n1b\n");
work_dir.write_file("file2", "1a\n");
work_dir.run_jj(["new", "-m2"]).success();
work_dir.write_file("file1", "1a\n");
work_dir.write_file("file2", "1a\n1b\n");
// These changes produce conflicts because
// - for file1, "1a\n" is deleted from the commit 1,
// - for file2, two consecutive hunks are deleted.
//
// Since file2 change is split to two separate hunks, the file deletion
// cannot be propagated. If we implement merging based on interleaved delta,
// the file2 change will apply cleanly. The file1 change might be split into
// "1a\n" deletion at the commit 1 and file deletion at the commit 2, but
// I'm not sure if that's intuitive.
work_dir.run_jj(["new"]).success();
work_dir.remove_file("file1");
work_dir.remove_file("file2");
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 2 revisions:
kkmpptxz ca0a3d3c (conflict) 2
qpvuntsm f2703d39 (conflict) 1
Rebased 1 descendant commits.
Working copy (@) now at: zsuskuln 0156c3af (no description set)
Parent commit (@-) : kkmpptxz ca0a3d3c (conflict) 2
New conflicts appeared in 2 commits:
kkmpptxz ca0a3d3c (conflict) 2
qpvuntsm f2703d39 (conflict) 1
Hint: To resolve the conflicts, start by updating to the first one:
jj new qpvuntsm
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
Remaining changes:
D file2
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ zsuskuln 0156c3af (no description set)
│ diff --git a/file2 b/file2
│ deleted file mode 100644
│ index 0000000000..0000000000
│ --- a/file2
│ +++ /dev/null
│ @@ -1,7 +0,0 @@
│ -<<<<<<< Conflict 1 of 1
│ -%%%%%%% Changes from base to side #1
│ --1a
│ - 1b
│ -+++++++ Contents of side #2
│ -1a
│ ->>>>>>> Conflict 1 of 1 ends
× kkmpptxz ca0a3d3c (conflict) 2
│ diff --git a/file1 b/file1
│ deleted file mode 100644
│ index 0000000000..0000000000
│ --- a/file1
│ +++ /dev/null
│ @@ -1,6 +0,0 @@
│ -<<<<<<< Conflict 1 of 1
│ -%%%%%%% Changes from base to side #1
│ - 1a
│ -+1b
│ -+++++++ Contents of side #2
│ ->>>>>>> Conflict 1 of 1 ends
│ diff --git a/file2 b/file2
│ --- a/file2
│ +++ b/file2
│ @@ -1,7 +1,7 @@
│ <<<<<<< Conflict 1 of 1
│ %%%%%%% Changes from base to side #1
│ - 1a
│ --1b
│ +-1a
│ + 1b
│ +++++++ Contents of side #2
│ -1b
│ +1a
│ >>>>>>> Conflict 1 of 1 ends
× qpvuntsm f2703d39 (conflict) 1
│ diff --git a/file1 b/file1
~ new file mode 100644
index 0000000000..0000000000
--- /dev/null
+++ b/file1
@@ -0,0 +1,6 @@
+<<<<<<< Conflict 1 of 1
+%%%%%%% Changes from base to side #1
+ 1a
++1b
++++++++ Contents of side #2
+>>>>>>> Conflict 1 of 1 ends
diff --git a/file2 b/file2
new file mode 100644
index 0000000000..0000000000
--- /dev/null
+++ b/file2
@@ -0,0 +1,7 @@
+<<<<<<< Conflict 1 of 1
+%%%%%%% Changes from base to side #1
+ 1a
+-1b
++++++++ Contents of side #2
+1b
+>>>>>>> Conflict 1 of 1 ends
[EOF]
");
}
#[test]
fn test_absorb_file_mode() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m1"]).success();
work_dir.write_file("file1", "1a\n");
work_dir.run_jj(["file", "chmod", "x", "file1"]).success();
// Modify content and mode
work_dir.run_jj(["new"]).success();
work_dir.write_file("file1", "1A\n");
work_dir.run_jj(["file", "chmod", "n", "file1"]).success();
// Mode change shouldn't be absorbed
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
qpvuntsm 991365da 1
Rebased 1 descendant commits.
Working copy (@) now at: zsuskuln 77de368e (no description set)
Parent commit (@-) : qpvuntsm 991365da 1
Remaining changes:
M file1
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ zsuskuln 77de368e (no description set)
│ diff --git a/file1 b/file1
│ old mode 100755
│ new mode 100644
○ qpvuntsm 991365da 1
│ diff --git a/file1 b/file1
~ new file mode 100755
index 0000000000..268de3f3ec
--- /dev/null
+++ b/file1
@@ -0,0 +1,1 @@
+1A
[EOF]
");
}
#[test]
fn test_absorb_from_into() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["new", "-m1"]).success();
work_dir.write_file("file1", "1a\n1b\n1c\n");
work_dir.run_jj(["new", "-m2"]).success();
work_dir.write_file("file1", "1a\n2a\n1b\n1c\n2b\n");
// Line "X" and "Z" have unambiguous adjacent line within the destinations
// range. Line "Y" doesn't have such line.
work_dir.run_jj(["new"]).success();
work_dir.write_file("file1", "1a\nX\n2a\n1b\nY\n1c\n2b\nZ\n");
let output = work_dir.run_jj(["absorb", "--into=@-"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
kkmpptxz 91df4543 2
Rebased 1 descendant commits.
Working copy (@) now at: zsuskuln d5424357 (no description set)
Parent commit (@-) : kkmpptxz 91df4543 2
Remaining changes:
M file1
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "@-::"), @r"
@ zsuskuln d5424357 (no description set)
│ diff --git a/file1 b/file1
│ index faf62af049..c2d0b12547 100644
│ --- a/file1
│ +++ b/file1
│ @@ -2,6 +2,7 @@
│ X
│ 2a
│ 1b
│ +Y
│ 1c
│ 2b
│ Z
○ kkmpptxz 91df4543 2
│ diff --git a/file1 b/file1
~ index 352e9b3794..faf62af049 100644
--- a/file1
+++ b/file1
@@ -1,3 +1,7 @@
1a
+X
+2a
1b
1c
+2b
+Z
[EOF]
");
// Absorb all lines from the working-copy parent. An empty commit won't be
// discarded because "absorb" isn't a command to squash commit descriptions.
let output = work_dir.run_jj(["absorb", "--from=@-"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
rlvkpnrz 3a5fd02e 1
Rebased 2 descendant commits.
Working copy (@) now at: zsuskuln 53ce490b (no description set)
Parent commit (@-) : kkmpptxz c94cd773 (empty) 2
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ zsuskuln 53ce490b (no description set)
│ diff --git a/file1 b/file1
│ index faf62af049..c2d0b12547 100644
│ --- a/file1
│ +++ b/file1
│ @@ -2,6 +2,7 @@
│ X
│ 2a
│ 1b
│ +Y
│ 1c
│ 2b
│ Z
○ kkmpptxz c94cd773 (empty) 2
○ rlvkpnrz 3a5fd02e 1
│ diff --git a/file1 b/file1
│ new file mode 100644
│ index 0000000000..faf62af049
│ --- /dev/null
│ +++ b/file1
│ @@ -0,0 +1,7 @@
│ +1a
│ +X
│ +2a
│ +1b
│ +1c
│ +2b
│ +Z
○ qpvuntsm 230dd059 (empty) (no description set)
~
[EOF]
");
}
#[test]
fn test_absorb_paths() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m1"]).success();
work_dir.write_file("file1", "1a\n");
work_dir.write_file("file2", "1a\n");
// Modify both files
work_dir.run_jj(["new"]).success();
work_dir.write_file("file1", "1A\n");
work_dir.write_file("file2", "1A\n");
let output = work_dir.run_jj(["absorb", "unknown"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["absorb", "file1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
qpvuntsm ae044adb 1
Rebased 1 descendant commits.
Working copy (@) now at: kkmpptxz c6f31836 (no description set)
Parent commit (@-) : qpvuntsm ae044adb 1
Remaining changes:
M file2
[EOF]
");
insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r"
@ kkmpptxz c6f31836 (no description set)
│ diff --git a/file2 b/file2
│ index a8994dc188..268de3f3ec 100644
│ --- a/file2
│ +++ b/file2
│ @@ -1,1 +1,1 @@
│ -1a
│ +1A
○ qpvuntsm ae044adb 1
│ diff --git a/file1 b/file1
~ new file mode 100644
index 0000000000..268de3f3ec
--- /dev/null
+++ b/file1
@@ -0,0 +1,1 @@
+1A
diff --git a/file2 b/file2
new file mode 100644
index 0000000000..a8994dc188
--- /dev/null
+++ b/file2
@@ -0,0 +1,1 @@
+1a
[EOF]
");
}
#[test]
fn test_absorb_immutable() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
test_env.add_config("revset-aliases.'immutable_heads()' = 'present(main)'");
work_dir.run_jj(["describe", "-m1"]).success();
work_dir.write_file("file1", "1a\n1b\n");
work_dir.run_jj(["new", "-m2"]).success();
work_dir
.run_jj(["bookmark", "set", "-r@-", "main"])
.success();
work_dir.write_file("file1", "1a\n1b\n2a\n2b\n");
work_dir.run_jj(["new"]).success();
work_dir.write_file("file1", "1A\n1b\n2a\n2B\n");
// Immutable revisions are excluded by default
let output = work_dir.run_jj(["absorb"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Absorbed changes into 1 revisions:
kkmpptxz d80e3c2a 2
Rebased 1 descendant commits.
Working copy (@) now at: mzvwutvl 3021153d (no description set)
Parent commit (@-) : kkmpptxz d80e3c2a 2
Remaining changes:
M file1
[EOF]
");
// Immutable revisions shouldn't be rewritten
let output = work_dir.run_jj(["absorb", "--into=all()"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Error: Commit 3619e4e52fce is immutable
Hint: Could not modify commit: qpvuntsm 3619e4e5 main | 1
Hint: Immutable commits are used to protect shared history.
Hint: For more information, see:
- https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits
- `jj help -k config`, "Set of immutable commits"
Hint: This operation would rewrite 1 immutable commits.
[EOF]
[exit status: 1]
"#);
insta::assert_snapshot!(get_diffs(&work_dir, ".."), @r"
@ mzvwutvl 3021153d (no description set)
│ diff --git a/file1 b/file1
│ index 75e4047831..428796ca20 100644
│ --- a/file1
│ +++ b/file1
│ @@ -1,4 +1,4 @@
│ -1a
│ +1A
│ 1b
│ 2a
│ 2B
○ kkmpptxz d80e3c2a 2
│ diff --git a/file1 b/file1
│ index 8c5268f893..75e4047831 100644
│ --- a/file1
│ +++ b/file1
│ @@ -1,2 +1,4 @@
│ 1a
│ 1b
│ +2a
│ +2B
◆ qpvuntsm 3619e4e5 1
│ diff --git a/file1 b/file1
~ new file mode 100644
index 0000000000..8c5268f893
--- /dev/null
+++ b/file1
@@ -0,0 +1,2 @@
+1a
+1b
[EOF]
");
}
#[must_use]
fn get_diffs(work_dir: &TestWorkDir, revision: &str) -> CommandOutput {
let template = r#"format_commit_summary_with_refs(self, "") ++ "\n""#;
work_dir.run_jj(["log", "-r", revision, "-T", template, "--git"])
}
#[must_use]
fn get_evolog(work_dir: &TestWorkDir, revision: &str) -> CommandOutput {
let template = r#"format_commit_summary_with_refs(self, "") ++ "\n""#;
work_dir.run_jj(["evolog", "-r", revision, "-T", template])
}