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
19#[test]
20fn test_move() {
21 let test_env = TestEnvironment::default();
22 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
23 let repo_path = test_env.env_root().join("repo");
24
25 // Create history like this:
26 // F
27 // |
28 // E C
29 // | |
30 // D B
31 // |/
32 // A
33 //
34 // When moving changes between e.g. C and F, we should not get unrelated changes
35 // from B and D.
36 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
37 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
38 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
39 std::fs::write(repo_path.join("file3"), "a\n").unwrap();
40 test_env.jj_cmd_ok(&repo_path, &["new"]);
41 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
42 std::fs::write(repo_path.join("file3"), "b\n").unwrap();
43 test_env.jj_cmd_ok(&repo_path, &["new"]);
44 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
45 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
46 test_env.jj_cmd_ok(&repo_path, &["edit", "a"]);
47 test_env.jj_cmd_ok(&repo_path, &["new"]);
48 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "d"]);
49 std::fs::write(repo_path.join("file3"), "d\n").unwrap();
50 test_env.jj_cmd_ok(&repo_path, &["new"]);
51 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "e"]);
52 std::fs::write(repo_path.join("file2"), "e\n").unwrap();
53 test_env.jj_cmd_ok(&repo_path, &["new"]);
54 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "f"]);
55 std::fs::write(repo_path.join("file2"), "f\n").unwrap();
56 // Test the setup
57 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
58 @ 0d7353584003 f
59 ◉ e9515f21068c e
60 ◉ bdd835cae844 d
61 │ ◉ caa4d0b23201 c
62 │ ◉ 55171e33db26 b
63 ├─╯
64 ◉ 3db0a2f5b535 a
65 ◉ 000000000000
66 "###);
67
68 // Errors out without arguments
69 let stderr = test_env.jj_cmd_cli_error(&repo_path, &["move"]);
70 insta::assert_snapshot!(stderr, @r###"
71 error: the following required arguments were not provided:
72 <--from <FROM>|--to <TO>>
73
74 Usage: jj move <--from <FROM>|--to <TO>> [PATHS]...
75
76 For more information, try '--help'.
77 "###);
78 // Errors out if source and destination are the same
79 let stderr = test_env.jj_cmd_failure(&repo_path, &["move", "--to", "@"]);
80 insta::assert_snapshot!(stderr, @r###"
81 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
82 Warning: `jj move` will be removed in a future version, and this will be a hard error
83 Error: Source and destination cannot be the same.
84 "###);
85
86 // Can move from sibling, which results in the source being abandoned
87 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["move", "--from", "c"]);
88 insta::assert_snapshot!(stdout, @"");
89 insta::assert_snapshot!(stderr, @r###"
90 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
91 Warning: `jj move` will be removed in a future version, and this will be a hard error
92 Working copy now at: kmkuslsw 1c03e3d3 f | (no description set)
93 Parent commit : znkkpsqq e9515f21 e | (no description set)
94 Added 0 files, modified 1 files, removed 0 files
95 "###);
96 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
97 @ 1c03e3d3c63f f
98 ◉ e9515f21068c e
99 ◉ bdd835cae844 d
100 │ ◉ 55171e33db26 b c
101 ├─╯
102 ◉ 3db0a2f5b535 a
103 ◉ 000000000000
104 "###);
105 // The change from the source has been applied
106 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
107 insta::assert_snapshot!(stdout, @r###"
108 c
109 "###);
110 // File `file2`, which was not changed in source, is unchanged
111 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
112 insta::assert_snapshot!(stdout, @r###"
113 f
114 "###);
115
116 // Can move from ancestor
117 test_env.jj_cmd_ok(&repo_path, &["undo"]);
118 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["move", "--from", "@--"]);
119 insta::assert_snapshot!(stdout, @"");
120 insta::assert_snapshot!(stderr, @r###"
121 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
122 Warning: `jj move` will be removed in a future version, and this will be a hard error
123 Working copy now at: kmkuslsw c8d83075 f | (no description set)
124 Parent commit : znkkpsqq 2c50bfc5 e | (no description set)
125 "###);
126 // The change has been removed from the source (the change pointed to by 'd'
127 // became empty and was abandoned)
128 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
129 @ c8d83075e8c2 f
130 ◉ 2c50bfc59c68 e
131 │ ◉ caa4d0b23201 c
132 │ ◉ 55171e33db26 b
133 ├─╯
134 ◉ 3db0a2f5b535 a d
135 ◉ 000000000000
136 "###);
137 // The change from the source has been applied (the file contents were already
138 // "f", as is typically the case when moving changes from an ancestor)
139 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
140 insta::assert_snapshot!(stdout, @r###"
141 f
142 "###);
143
144 // Can move from descendant
145 test_env.jj_cmd_ok(&repo_path, &["undo"]);
146 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["move", "--from", "e", "--to", "d"]);
147 insta::assert_snapshot!(stdout, @"");
148 insta::assert_snapshot!(stderr, @r###"
149 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
150 Warning: `jj move` will be removed in a future version, and this will be a hard error
151 Rebased 1 descendant commits
152 Working copy now at: kmkuslsw 2b723b1d f | (no description set)
153 Parent commit : vruxwmqv 4293930d d e | (no description set)
154 "###);
155 // The change has been removed from the source (the change pointed to by 'e'
156 // became empty and was abandoned)
157 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
158 @ 2b723b1d6033 f
159 ◉ 4293930d6333 d e
160 │ ◉ caa4d0b23201 c
161 │ ◉ 55171e33db26 b
162 ├─╯
163 ◉ 3db0a2f5b535 a
164 ◉ 000000000000
165 "###);
166 // The change from the source has been applied
167 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "d"]);
168 insta::assert_snapshot!(stdout, @r###"
169 e
170 "###);
171}
172
173#[test]
174fn test_move_partial() {
175 let mut test_env = TestEnvironment::default();
176 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
177 let repo_path = test_env.env_root().join("repo");
178
179 // Create history like this:
180 // C
181 // |
182 // D B
183 // |/
184 // A
185 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
186 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
187 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
188 std::fs::write(repo_path.join("file3"), "a\n").unwrap();
189 test_env.jj_cmd_ok(&repo_path, &["new"]);
190 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
191 std::fs::write(repo_path.join("file3"), "b\n").unwrap();
192 test_env.jj_cmd_ok(&repo_path, &["new"]);
193 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
194 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
195 std::fs::write(repo_path.join("file2"), "c\n").unwrap();
196 test_env.jj_cmd_ok(&repo_path, &["edit", "a"]);
197 test_env.jj_cmd_ok(&repo_path, &["new"]);
198 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "d"]);
199 std::fs::write(repo_path.join("file3"), "d\n").unwrap();
200 // Test the setup
201 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
202 @ bdd835cae844 d
203 │ ◉ 5028db694b6b c
204 │ ◉ 55171e33db26 b
205 ├─╯
206 ◉ 3db0a2f5b535 a
207 ◉ 000000000000
208 "###);
209
210 let edit_script = test_env.set_up_fake_diff_editor();
211
212 // If we don't make any changes in the diff-editor, the whole change is moved
213 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["move", "-i", "--from", "c"]);
214 insta::assert_snapshot!(stdout, @"");
215 insta::assert_snapshot!(stderr, @r###"
216 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
217 Warning: `jj move` will be removed in a future version, and this will be a hard error
218 Working copy now at: vruxwmqv 71b69e43 d | (no description set)
219 Parent commit : qpvuntsm 3db0a2f5 a | (no description set)
220 Added 0 files, modified 2 files, removed 0 files
221 "###);
222 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
223 @ 71b69e433fbc d
224 │ ◉ 55171e33db26 b c
225 ├─╯
226 ◉ 3db0a2f5b535 a
227 ◉ 000000000000
228 "###);
229 // The changes from the source has been applied
230 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
231 insta::assert_snapshot!(stdout, @r###"
232 c
233 "###);
234 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
235 insta::assert_snapshot!(stdout, @r###"
236 c
237 "###);
238 // File `file3`, which was not changed in source, is unchanged
239 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file3"]);
240 insta::assert_snapshot!(stdout, @r###"
241 d
242 "###);
243
244 // Can move only part of the change in interactive mode
245 test_env.jj_cmd_ok(&repo_path, &["undo"]);
246 std::fs::write(&edit_script, "reset file2").unwrap();
247 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["move", "-i", "--from", "c"]);
248 insta::assert_snapshot!(stdout, @"");
249 insta::assert_snapshot!(stderr, @r###"
250 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
251 Warning: `jj move` will be removed in a future version, and this will be a hard error
252 Working copy now at: vruxwmqv 63f1a6e9 d | (no description set)
253 Parent commit : qpvuntsm 3db0a2f5 a | (no description set)
254 Added 0 files, modified 1 files, removed 0 files
255 "###);
256 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
257 @ 63f1a6e96edb d
258 │ ◉ d027c6e3e6bc c
259 │ ◉ 55171e33db26 b
260 ├─╯
261 ◉ 3db0a2f5b535 a
262 ◉ 000000000000
263 "###);
264 // The selected change from the source has been applied
265 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
266 insta::assert_snapshot!(stdout, @r###"
267 c
268 "###);
269 // The unselected change from the source has not been applied
270 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
271 insta::assert_snapshot!(stdout, @r###"
272 a
273 "###);
274 // File `file3`, which was changed in source's parent, is unchanged
275 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file3"]);
276 insta::assert_snapshot!(stdout, @r###"
277 d
278 "###);
279
280 // Can move only part of the change from a sibling in non-interactive mode
281 test_env.jj_cmd_ok(&repo_path, &["undo"]);
282 // Clear the script so we know it won't be used
283 std::fs::write(&edit_script, "").unwrap();
284 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["move", "--from", "c", "file1"]);
285 insta::assert_snapshot!(stdout, @"");
286 insta::assert_snapshot!(stderr, @r###"
287 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
288 Warning: `jj move` will be removed in a future version, and this will be a hard error
289 Working copy now at: vruxwmqv 17c2e663 d | (no description set)
290 Parent commit : qpvuntsm 3db0a2f5 a | (no description set)
291 Added 0 files, modified 1 files, removed 0 files
292 "###);
293 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
294 @ 17c2e6632cc5 d
295 │ ◉ 6a3ae047a03e c
296 │ ◉ 55171e33db26 b
297 ├─╯
298 ◉ 3db0a2f5b535 a
299 ◉ 000000000000
300 "###);
301 // The selected change from the source has been applied
302 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
303 insta::assert_snapshot!(stdout, @r###"
304 c
305 "###);
306 // The unselected change from the source has not been applied
307 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
308 insta::assert_snapshot!(stdout, @r###"
309 a
310 "###);
311 // File `file3`, which was changed in source's parent, is unchanged
312 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file3"]);
313 insta::assert_snapshot!(stdout, @r###"
314 d
315 "###);
316
317 // Can move only part of the change from a descendant in non-interactive mode
318 test_env.jj_cmd_ok(&repo_path, &["undo"]);
319 // Clear the script so we know it won't be used
320 std::fs::write(&edit_script, "").unwrap();
321 let (stdout, stderr) =
322 test_env.jj_cmd_ok(&repo_path, &["move", "--from", "c", "--to", "b", "file1"]);
323 insta::assert_snapshot!(stdout, @"");
324 insta::assert_snapshot!(stderr, @r###"
325 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
326 Warning: `jj move` will be removed in a future version, and this will be a hard error
327 Rebased 1 descendant commits
328 "###);
329 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
330 ◉ 21253406d416 c
331 ◉ e1cf08aae711 b
332 │ @ bdd835cae844 d
333 ├─╯
334 ◉ 3db0a2f5b535 a
335 ◉ 000000000000
336 "###);
337 // The selected change from the source has been applied
338 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "b"]);
339 insta::assert_snapshot!(stdout, @r###"
340 c
341 "###);
342 // The unselected change from the source has not been applied
343 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "b"]);
344 insta::assert_snapshot!(stdout, @r###"
345 a
346 "###);
347
348 // If we specify only a non-existent file, then nothing changes.
349 test_env.jj_cmd_ok(&repo_path, &["undo"]);
350 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["move", "--from", "c", "nonexistent"]);
351 insta::assert_snapshot!(stdout, @"");
352 insta::assert_snapshot!(stderr, @r###"
353 Warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent
354 Warning: `jj move` will be removed in a future version, and this will be a hard error
355 Nothing changed.
356 "###);
357}
358
359fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
360 let template = r#"commit_id.short() ++ " " ++ branches"#;
361 test_env.jj_cmd_success(cwd, &["log", "-T", template])
362}
363
364#[test]
365fn test_move_description() {
366 let mut test_env = TestEnvironment::default();
367 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
368 let repo_path = test_env.env_root().join("repo");
369
370 let edit_script = test_env.set_up_fake_editor();
371 std::fs::write(&edit_script, r#"fail"#).unwrap();
372
373 // If both descriptions are empty, the resulting description is empty
374 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
375 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
376 test_env.jj_cmd_ok(&repo_path, &["new"]);
377 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
378 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
379 test_env.jj_cmd_ok(&repo_path, &["move", "--to", "@-"]);
380 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @"");
381
382 // If the destination's description is empty and the source's description is
383 // non-empty, the resulting description is from the source
384 test_env.jj_cmd_ok(&repo_path, &["undo"]);
385 test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "source"]);
386 test_env.jj_cmd_ok(&repo_path, &["move", "--to", "@-"]);
387 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
388 source
389 "###);
390
391 // If the destination's description is non-empty and the source's description is
392 // empty, the resulting description is from the destination
393 test_env.jj_cmd_ok(&repo_path, &["op", "restore", "@--"]);
394 test_env.jj_cmd_ok(&repo_path, &["describe", "@-", "-m", "destination"]);
395 test_env.jj_cmd_ok(&repo_path, &["move", "--to", "@-"]);
396 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
397 destination
398 "###);
399
400 // If both descriptions were non-empty, we get asked for a combined description
401 test_env.jj_cmd_ok(&repo_path, &["undo"]);
402 test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "source"]);
403 std::fs::write(&edit_script, "dump editor0").unwrap();
404 test_env.jj_cmd_ok(&repo_path, &["move", "--to", "@-"]);
405 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
406 destination
407
408 source
409 "###);
410 insta::assert_snapshot!(
411 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @r###"
412 JJ: Enter a description for the combined commit.
413 JJ: Description from the destination commit:
414 destination
415
416 JJ: Description from source commit:
417 source
418
419 JJ: Lines starting with "JJ: " (like this one) will be removed.
420 "###);
421
422 // If the source's *content* doesn't become empty, then the source remains and
423 // both descriptions are unchanged
424 test_env.jj_cmd_ok(&repo_path, &["undo"]);
425 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
426 test_env.jj_cmd_ok(&repo_path, &["move", "--to", "@-", "file1"]);
427 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
428 destination
429 "###);
430 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @r###"
431 source
432 "###);
433}
434
435fn get_description(test_env: &TestEnvironment, repo_path: &Path, rev: &str) -> String {
436 test_env.jj_cmd_success(
437 repo_path,
438 &["log", "--no-graph", "-T", "description", "-r", rev],
439 )
440}