just playing with tangled
1// Copyright 2022 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::path::Path;
16
17use crate::common::TestEnvironment;
18
19pub mod common;
20
21#[test]
22fn test_unsquash() {
23 let test_env = TestEnvironment::default();
24 test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
25 let repo_path = test_env.env_root().join("repo");
26
27 test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
28 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
29 test_env.jj_cmd_success(&repo_path, &["new"]);
30 test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
31 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
32 test_env.jj_cmd_success(&repo_path, &["new"]);
33 test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
34 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
35 // Test the setup
36 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
37 @ 90fe0a96fc90 c
38 ◉ fa5efbdf533c b
39 ◉ 90aeefd03044 a
40 ◉ 000000000000
41 "###);
42
43 // Unsquashes into the working copy from its parent by default
44 let stdout = test_env.jj_cmd_success(&repo_path, &["unsquash"]);
45 insta::assert_snapshot!(stdout, @r###"
46 Working copy now at: 1b10d78f6136 (no description set) |c
47 Parent commit : 90aeefd03044 (no description set) |a b
48 "###);
49 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
50 @ 1b10d78f6136 c
51 ◉ 90aeefd03044 a b
52 ◉ 000000000000
53 "###);
54 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
55 insta::assert_snapshot!(stdout, @r###"
56 c
57 "###);
58
59 // Can unsquash into a given commit from its parent
60 test_env.jj_cmd_success(&repo_path, &["undo"]);
61 let stdout = test_env.jj_cmd_success(&repo_path, &["unsquash", "-r", "b"]);
62 insta::assert_snapshot!(stdout, @r###"
63 Rebased 1 descendant commits
64 Working copy now at: 45b8b3ddc25a (no description set) |c
65 Parent commit : 9146bcc8d996 (no description set) |b
66 "###);
67 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
68 @ 45b8b3ddc25a c
69 ◉ 9146bcc8d996 b
70 ◉ 000000000000 a
71 "###);
72 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "b"]);
73 insta::assert_snapshot!(stdout, @r###"
74 b
75 "###);
76 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
77 insta::assert_snapshot!(stdout, @r###"
78 c
79 "###);
80
81 // Cannot unsquash into a merge commit (because it's unclear which parent it
82 // should come from)
83 test_env.jj_cmd_success(&repo_path, &["undo"]);
84 test_env.jj_cmd_success(&repo_path, &["edit", "b"]);
85 test_env.jj_cmd_success(&repo_path, &["new"]);
86 test_env.jj_cmd_success(&repo_path, &["branch", "create", "d"]);
87 std::fs::write(repo_path.join("file2"), "d\n").unwrap();
88 test_env.jj_cmd_success(&repo_path, &["new", "-m", "merge", "c", "d"]);
89 test_env.jj_cmd_success(&repo_path, &["branch", "create", "e"]);
90 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
91 @ 1f8f152ff48e e
92 ├─╮
93 │ ◉ 5658521e0f8b d
94 ◉ │ 90fe0a96fc90 c
95 ├─╯
96 ◉ fa5efbdf533c b
97 ◉ 90aeefd03044 a
98 ◉ 000000000000
99 "###);
100 let stderr = test_env.jj_cmd_failure(&repo_path, &["unsquash"]);
101 insta::assert_snapshot!(stderr, @r###"
102 Error: Cannot unsquash merge commits
103 "###);
104
105 // Can unsquash from a merge commit
106 test_env.jj_cmd_success(&repo_path, &["co", "e"]);
107 std::fs::write(repo_path.join("file1"), "e\n").unwrap();
108 let stdout = test_env.jj_cmd_success(&repo_path, &["unsquash"]);
109 insta::assert_snapshot!(stdout, @r###"
110 Working copy now at: 3217340cb761 merge
111 Parent commit : 90fe0a96fc90 (no description set) |c e??
112 Parent commit : 5658521e0f8b (no description set) |d e??
113 "###);
114 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
115 @ 3217340cb761
116 ├─╮
117 │ ◉ 5658521e0f8b d e??
118 ◉ │ 90fe0a96fc90 c e??
119 ├─╯
120 ◉ fa5efbdf533c b
121 ◉ 90aeefd03044 a
122 ◉ 000000000000
123 "###);
124 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
125 insta::assert_snapshot!(stdout, @r###"
126 e
127 "###);
128}
129
130#[test]
131fn test_unsquash_partial() {
132 let mut test_env = TestEnvironment::default();
133 test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
134 let repo_path = test_env.env_root().join("repo");
135
136 test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
137 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
138 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
139 test_env.jj_cmd_success(&repo_path, &["new"]);
140 test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
141 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
142 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
143 test_env.jj_cmd_success(&repo_path, &["new"]);
144 test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
145 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
146 std::fs::write(repo_path.join("file2"), "c\n").unwrap();
147 // Test the setup
148 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
149 @ d989314f3df0 c
150 ◉ 2a2d19a3283f b
151 ◉ 47a1e795d146 a
152 ◉ 000000000000
153 "###);
154
155 // If we don't make any changes in the diff-editor, the whole change is moved
156 // from the parent
157 let edit_script = test_env.set_up_fake_diff_editor();
158 let stdout = test_env.jj_cmd_success(&repo_path, &["unsquash", "-r", "b", "-i"]);
159 insta::assert_snapshot!(stdout, @r###"
160 Rebased 1 descendant commits
161 Working copy now at: 37c961d0d1e2 (no description set) |c
162 Parent commit : 000af22057b9 (no description set) |b
163 "###);
164 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
165 @ 37c961d0d1e2 c
166 ◉ 000af22057b9 b
167 ◉ ee67504598b6 a
168 ◉ 000000000000
169 "###);
170 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "a"]);
171 insta::assert_snapshot!(stdout, @r###"
172 a
173 "###);
174
175 // Can unsquash only some changes in interactive mode
176 test_env.jj_cmd_success(&repo_path, &["undo"]);
177 std::fs::write(edit_script, "reset file1").unwrap();
178 let stdout = test_env.jj_cmd_success(&repo_path, &["unsquash", "-i"]);
179 insta::assert_snapshot!(stdout, @r###"
180 Working copy now at: a8e8fded1021 (no description set) |c
181 Parent commit : 46cc06672a99 (no description set) |b
182 "###);
183 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
184 @ a8e8fded1021 c
185 ◉ 46cc06672a99 b
186 ◉ 47a1e795d146 a
187 ◉ 000000000000
188 "###);
189 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "b"]);
190 insta::assert_snapshot!(stdout, @r###"
191 a
192 "###);
193 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "b"]);
194 insta::assert_snapshot!(stdout, @r###"
195 b
196 "###);
197 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "c"]);
198 insta::assert_snapshot!(stdout, @r###"
199 c
200 "###);
201 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "c"]);
202 insta::assert_snapshot!(stdout, @r###"
203 c
204 "###);
205}
206
207fn get_log_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
208 let template = r#"commit_id.short() ++ " " ++ branches"#;
209 test_env.jj_cmd_success(repo_path, &["log", "-T", template])
210}
211
212#[test]
213fn test_unsquash_description() {
214 let mut test_env = TestEnvironment::default();
215 test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
216 let repo_path = test_env.env_root().join("repo");
217
218 let edit_script = test_env.set_up_fake_editor();
219 std::fs::write(&edit_script, r#"fail"#).unwrap();
220
221 // If both descriptions are empty, the resulting description is empty
222 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
223 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
224 test_env.jj_cmd_success(&repo_path, &["new"]);
225 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
226 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
227 test_env.jj_cmd_success(&repo_path, &["unsquash"]);
228 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @"");
229
230 // If the destination's description is empty and the source's description is
231 // non-empty, the resulting description is from the source
232 test_env.jj_cmd_success(&repo_path, &["undo"]);
233 test_env.jj_cmd_success(&repo_path, &["describe", "@-", "-m", "source"]);
234 test_env.jj_cmd_success(&repo_path, &["unsquash"]);
235 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @r###"
236 source
237 "###);
238
239 // If the destination description is non-empty and the source's description is
240 // empty, the resulting description is from the destination
241 test_env.jj_cmd_success(&repo_path, &["op", "restore", "@--"]);
242 test_env.jj_cmd_success(&repo_path, &["describe", "-m", "destination"]);
243 test_env.jj_cmd_success(&repo_path, &["unsquash"]);
244 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @r###"
245 destination
246 "###);
247
248 // If both descriptions were non-empty, we get asked for a combined description
249 test_env.jj_cmd_success(&repo_path, &["undo"]);
250 test_env.jj_cmd_success(&repo_path, &["describe", "@-", "-m", "source"]);
251 std::fs::write(&edit_script, "dump editor0").unwrap();
252 test_env.jj_cmd_success(&repo_path, &["unsquash"]);
253 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @r###"
254 destination
255
256 source
257 "###);
258 insta::assert_snapshot!(
259 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @r###"
260 JJ: Enter a description for the combined commit.
261 JJ: Description from the destination commit:
262 destination
263
264 JJ: Description from the source commit:
265 source
266
267 JJ: Lines starting with "JJ: " (like this one) will be removed.
268 "###);
269
270 // If the source's *content* doesn't become empty, then the source remains and
271 // both descriptions are unchanged
272 test_env.jj_cmd_success(&repo_path, &["undo"]);
273 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
274 source
275 "###);
276 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @r###"
277 destination
278 "###);
279}
280
281fn get_description(test_env: &TestEnvironment, repo_path: &Path, rev: &str) -> String {
282 test_env.jj_cmd_success(
283 repo_path,
284 &["log", "--no-graph", "-T", "description", "-r", rev],
285 )
286}