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
19fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
20 let template = r#"separate(" ", change_id.short(), empty, description, local_branches)"#;
21 test_env.jj_cmd_success(cwd, &["log", "-T", template])
22}
23
24fn get_recorded_dates(test_env: &TestEnvironment, cwd: &Path, revset: &str) -> String {
25 let template = r#"separate("\n", "Author date: " ++ author.timestamp(), "Committer date: " ++ committer.timestamp())"#;
26 test_env.jj_cmd_success(cwd, &["log", "--no-graph", "-T", template, "-r", revset])
27}
28
29#[test]
30fn test_split_by_paths() {
31 let mut test_env = TestEnvironment::default();
32 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
33 let repo_path = test_env.env_root().join("repo");
34
35 std::fs::write(repo_path.join("file1"), "foo").unwrap();
36 std::fs::write(repo_path.join("file2"), "foo").unwrap();
37 std::fs::write(repo_path.join("file3"), "foo").unwrap();
38
39 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
40 @ qpvuntsmwlqt false
41 ◉ zzzzzzzzzzzz true
42 "###);
43 insta::assert_snapshot!(get_recorded_dates(&test_env, &repo_path,"@"), @r###"
44 Author date: 2001-02-03 04:05:07.000 +07:00
45 Committer date: 2001-02-03 04:05:08.000 +07:00
46 "###);
47
48 let edit_script = test_env.set_up_fake_editor();
49 std::fs::write(
50 edit_script,
51 ["dump editor0", "next invocation\n", "dump editor1"].join("\0"),
52 )
53 .unwrap();
54 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["split", "file2"]);
55 insta::assert_snapshot!(stdout, @"");
56 insta::assert_snapshot!(stderr, @r###"
57 First part: qpvuntsm d62c056f (no description set)
58 Second part: zsuskuln 5a32af4a (no description set)
59 Working copy now at: zsuskuln 5a32af4a (no description set)
60 Parent commit : qpvuntsm d62c056f (no description set)
61 "###);
62 insta::assert_snapshot!(
63 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @r###"
64 JJ: Enter a description for the first commit.
65
66 JJ: This commit contains the following changes:
67 JJ: A file2
68
69 JJ: Lines starting with "JJ: " (like this one) will be removed.
70 "###);
71 assert!(!test_env.env_root().join("editor1").exists());
72
73 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
74 @ zsuskulnrvyr false
75 ◉ qpvuntsmwlqt false
76 ◉ zzzzzzzzzzzz true
77 "###);
78
79 // The author dates of the new commits should be inherited from the commit being
80 // split. The committer dates should be newer.
81 insta::assert_snapshot!(get_recorded_dates(&test_env, &repo_path,"@"), @r###"
82 Author date: 2001-02-03 04:05:07.000 +07:00
83 Committer date: 2001-02-03 04:05:10.000 +07:00
84 "###);
85 insta::assert_snapshot!(get_recorded_dates(&test_env, &repo_path,"@-"), @r###"
86 Author date: 2001-02-03 04:05:07.000 +07:00
87 Committer date: 2001-02-03 04:05:10.000 +07:00
88 "###);
89
90 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s", "-r", "@-"]);
91 insta::assert_snapshot!(stdout, @r###"
92 A file2
93 "###);
94 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
95 insta::assert_snapshot!(stdout, @r###"
96 A file1
97 A file3
98 "###);
99
100 // Insert an empty commit after @- with "split ."
101 test_env.set_up_fake_editor();
102 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["split", "-r", "@-", "."]);
103 insta::assert_snapshot!(stdout, @"");
104 insta::assert_snapshot!(stderr, @r###"
105 Rebased 1 descendant commits
106 First part: qpvuntsm b76d731d (no description set)
107 Second part: znkkpsqq 924604b2 (empty) (no description set)
108 Working copy now at: zsuskuln fffe30fb (no description set)
109 Parent commit : znkkpsqq 924604b2 (empty) (no description set)
110 "###);
111
112 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
113 @ zsuskulnrvyr false
114 ◉ znkkpsqqskkl true
115 ◉ qpvuntsmwlqt false
116 ◉ zzzzzzzzzzzz true
117 "###);
118
119 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s", "-r", "@--"]);
120 insta::assert_snapshot!(stdout, @r###"
121 A file2
122 "###);
123
124 // Remove newly created empty commit
125 test_env.jj_cmd_ok(&repo_path, &["abandon", "@-"]);
126
127 // Insert an empty commit before @- with "split nonexistent"
128 test_env.set_up_fake_editor();
129 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["split", "-r", "@-", "nonexistent"]);
130 insta::assert_snapshot!(stdout, @"");
131 insta::assert_snapshot!(stderr, @r###"
132 Warning: The given paths do not match any file: nonexistent
133 Rebased 1 descendant commits
134 First part: qpvuntsm 7086b0bc (empty) (no description set)
135 Second part: lylxulpl 2252ed18 (no description set)
136 Working copy now at: zsuskuln a3f2136a (no description set)
137 Parent commit : lylxulpl 2252ed18 (no description set)
138 "###);
139
140 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
141 @ zsuskulnrvyr false
142 ◉ lylxulplsnyw false
143 ◉ qpvuntsmwlqt true
144 ◉ zzzzzzzzzzzz true
145 "###);
146
147 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s", "-r", "@-"]);
148 insta::assert_snapshot!(stdout, @r###"
149 A file2
150 "###);
151}
152
153#[test]
154fn test_split_with_non_empty_description() {
155 let mut test_env = TestEnvironment::default();
156 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
157 test_env.add_config(r#"ui.default-description = "\n\nTESTED=TODO""#);
158 let workspace_path = test_env.env_root().join("repo");
159
160 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
161 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
162 test_env.jj_cmd_ok(&workspace_path, &["describe", "-m", "test"]);
163 let edit_script = test_env.set_up_fake_editor();
164 std::fs::write(
165 edit_script,
166 [
167 "dump editor1",
168 "write\npart 1",
169 "next invocation\n",
170 "dump editor2",
171 "write\npart 2",
172 ]
173 .join("\0"),
174 )
175 .unwrap();
176 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_path, &["split", "file1"]);
177 insta::assert_snapshot!(stdout, @"");
178 insta::assert_snapshot!(stderr, @r###"
179 First part: qpvuntsm 41e04d04 part 1
180 Second part: kkmpptxz 093b6c0d part 2
181 Working copy now at: kkmpptxz 093b6c0d part 2
182 Parent commit : qpvuntsm 41e04d04 part 1
183 "###);
184
185 assert_eq!(
186 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(),
187 r#"JJ: Enter a description for the first commit.
188test
189
190JJ: This commit contains the following changes:
191JJ: A file1
192
193JJ: Lines starting with "JJ: " (like this one) will be removed.
194"#
195 );
196 assert_eq!(
197 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(),
198 r#"JJ: Enter a description for the second commit.
199test
200
201JJ: This commit contains the following changes:
202JJ: A file2
203
204JJ: Lines starting with "JJ: " (like this one) will be removed.
205"#
206 );
207 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
208 @ kkmpptxzrspx false part 2
209 ◉ qpvuntsmwlqt false part 1
210 ◉ zzzzzzzzzzzz true
211 "###);
212}
213
214#[test]
215fn test_split_with_default_description() {
216 let mut test_env = TestEnvironment::default();
217 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
218 test_env.add_config(r#"ui.default-description = "\n\nTESTED=TODO""#);
219 let workspace_path = test_env.env_root().join("repo");
220
221 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
222 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
223
224 // Create a branch pointing to the commit. It will be moved to the second
225 // commit after the split.
226 test_env.jj_cmd_ok(&workspace_path, &["branch", "create", "test_branch"]);
227
228 let edit_script = test_env.set_up_fake_editor();
229 std::fs::write(
230 edit_script,
231 ["dump editor1", "next invocation\n", "dump editor2"].join("\0"),
232 )
233 .unwrap();
234 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_path, &["split", "file1"]);
235 insta::assert_snapshot!(stdout, @"");
236 insta::assert_snapshot!(stderr, @r###"
237 First part: qpvuntsm 5afe936c TESTED=TODO
238 Second part: kkmpptxz 0e09a2df test_branch | (no description set)
239 Working copy now at: kkmpptxz 0e09a2df test_branch | (no description set)
240 Parent commit : qpvuntsm 5afe936c TESTED=TODO
241 "###);
242
243 // Since the commit being split has no description, the user will only be
244 // prompted to add a description to the first commit, which will use the
245 // default value we set. The second commit will inherit the empty
246 // description from the commit being split.
247 assert_eq!(
248 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(),
249 r#"JJ: Enter a description for the first commit.
250
251
252TESTED=TODO
253JJ: This commit contains the following changes:
254JJ: A file1
255
256JJ: Lines starting with "JJ: " (like this one) will be removed.
257"#
258 );
259 assert!(!test_env.env_root().join("editor2").exists());
260 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
261 @ kkmpptxzrspx false test_branch
262 ◉ qpvuntsmwlqt false TESTED=TODO
263 ◉ zzzzzzzzzzzz true
264 "###);
265}
266
267// This test makes sure that the children of the commit being split retain any
268// other parents which weren't involved in the split.
269#[test]
270fn test_split_with_merge_child() {
271 let mut test_env = TestEnvironment::default();
272 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
273 let workspace_path = test_env.env_root().join("repo");
274 test_env.jj_cmd_ok(&workspace_path, &["describe", "-m=1"]);
275 test_env.jj_cmd_ok(&workspace_path, &["new", "root()", "-m=a"]);
276 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
277 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
278 test_env.jj_cmd_ok(
279 &workspace_path,
280 &["new", "description(1)", "description(a)", "-m=2"],
281 );
282 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
283 @ zsuskulnrvyr true 2
284 ├─╮
285 │ ◉ kkmpptxzrspx false a
286 ◉ │ qpvuntsmwlqt true 1
287 ├─╯
288 ◉ zzzzzzzzzzzz true
289 "###);
290
291 // Set up the editor and do the split.
292 let edit_script = test_env.set_up_fake_editor();
293 std::fs::write(
294 edit_script,
295 ["write\nAdd file1", "next invocation\n", "write\nAdd file2"].join("\0"),
296 )
297 .unwrap();
298 let (stdout, stderr) =
299 test_env.jj_cmd_ok(&workspace_path, &["split", "-r", "description(a)", "file1"]);
300 insta::assert_snapshot!(stdout, @"");
301 insta::assert_snapshot!(stderr, @r###"
302 Rebased 1 descendant commits
303 First part: kkmpptxz e8006b47 Add file1
304 Second part: royxmykx 5e1b793d Add file2
305 Working copy now at: zsuskuln 0315e471 (empty) 2
306 Parent commit : qpvuntsm dc0e5d61 (empty) 1
307 Parent commit : royxmykx 5e1b793d Add file2
308 "###);
309 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
310 @ zsuskulnrvyr true 2
311 ├─╮
312 │ ◉ royxmykxtrkr false Add file2
313 │ ◉ kkmpptxzrspx false Add file1
314 ◉ │ qpvuntsmwlqt true 1
315 ├─╯
316 ◉ zzzzzzzzzzzz true
317 "###);
318}
319
320#[test]
321// Split a commit with no descendants into siblings. Also tests that the default
322// description is set correctly on the first commit.
323fn test_split_siblings_no_descendants() {
324 let mut test_env = TestEnvironment::default();
325 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
326 test_env.add_config(r#"ui.default-description = "\n\nTESTED=TODO""#);
327 let workspace_path = test_env.env_root().join("repo");
328
329 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
330 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
331
332 // Create a branch pointing to the commit. It will be moved to the second
333 // commit after the split.
334 test_env.jj_cmd_ok(&workspace_path, &["branch", "create", "test_branch"]);
335 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
336 @ qpvuntsmwlqt false test_branch
337 ◉ zzzzzzzzzzzz true
338 "###);
339
340 let edit_script = test_env.set_up_fake_editor();
341 std::fs::write(
342 edit_script,
343 ["dump editor1", "next invocation\n", "dump editor2"].join("\0"),
344 )
345 .unwrap();
346 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_path, &["split", "--siblings", "file1"]);
347 insta::assert_snapshot!(stdout, @"");
348 insta::assert_snapshot!(stderr, @r###"
349 First part: qpvuntsm 8d2b7558 TESTED=TODO
350 Second part: zsuskuln acd41528 test_branch | (no description set)
351 Working copy now at: zsuskuln acd41528 test_branch | (no description set)
352 Parent commit : zzzzzzzz 00000000 (empty) (no description set)
353 Added 0 files, modified 0 files, removed 1 files
354 "###);
355 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
356 @ zsuskulnrvyr false test_branch
357 │ ◉ qpvuntsmwlqt false TESTED=TODO
358 ├─╯
359 ◉ zzzzzzzzzzzz true
360 "###);
361
362 // Since the commit being split has no description, the user will only be
363 // prompted to add a description to the first commit, which will use the
364 // default value we set. The second commit will inherit the empty
365 // description from the commit being split.
366 assert_eq!(
367 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(),
368 r#"JJ: Enter a description for the first commit.
369
370
371TESTED=TODO
372JJ: This commit contains the following changes:
373JJ: A file1
374
375JJ: Lines starting with "JJ: " (like this one) will be removed.
376"#
377 );
378 assert!(!test_env.env_root().join("editor2").exists());
379}
380
381#[test]
382fn test_split_siblings_with_descendants() {
383 // Configure the environment and make the initial commits.
384 let mut test_env = TestEnvironment::default();
385 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
386 // test_env.add_config(r#"ui.default-description = "\n\nTESTED=TODO""#);
387 let workspace_path = test_env.env_root().join("repo");
388
389 // First commit. This is the one we will split later.
390 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
391 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
392 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "Add file1 & file2"]);
393 // Second commit. This will be the child of the sibling commits after the split.
394 std::fs::write(workspace_path.join("file3"), "baz\n").unwrap();
395 test_env.jj_cmd_ok(&workspace_path, &["commit", "-m", "Add file3"]);
396 // Third commit.
397 std::fs::write(workspace_path.join("file4"), "foobarbaz\n").unwrap();
398 test_env.jj_cmd_ok(&workspace_path, &["describe", "-m", "Add file4"]);
399 // Move back to the previous commit so that we don't have to pass a revision
400 // to the split command.
401 test_env.jj_cmd_ok(&workspace_path, &["prev", "--edit"]);
402 test_env.jj_cmd_ok(&workspace_path, &["prev", "--edit"]);
403 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
404 ◉ kkmpptxzrspx false Add file4
405 ◉ rlvkpnrzqnoo false Add file3
406 @ qpvuntsmwlqt false Add file1 & file2
407 ◉ zzzzzzzzzzzz true
408 "###);
409
410 // Set up the editor and do the split.
411 let edit_script = test_env.set_up_fake_editor();
412 std::fs::write(
413 edit_script,
414 [
415 "dump editor1",
416 "write\nAdd file1",
417 "next invocation\n",
418 "dump editor2",
419 "write\nAdd file2",
420 ]
421 .join("\0"),
422 )
423 .unwrap();
424 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_path, &["split", "--siblings", "file1"]);
425 insta::assert_snapshot!(stdout, @"");
426 insta::assert_snapshot!(stderr, @r###"
427 Rebased 2 descendant commits
428 First part: qpvuntsm 27b151c3 Add file1
429 Second part: vruxwmqv c0857cfb Add file2
430 Working copy now at: vruxwmqv c0857cfb Add file2
431 Parent commit : zzzzzzzz 00000000 (empty) (no description set)
432 Added 0 files, modified 0 files, removed 1 files
433 "###);
434 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
435 ◉ kkmpptxzrspx false Add file4
436 ◉ rlvkpnrzqnoo false Add file3
437 ├─╮
438 │ @ vruxwmqvtpmx false Add file2
439 ◉ │ qpvuntsmwlqt false Add file1
440 ├─╯
441 ◉ zzzzzzzzzzzz true
442 "###);
443
444 // The commit we're splitting has a description, so the user will be
445 // prompted to enter a description for each of the sibling commits.
446 assert_eq!(
447 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(),
448 r#"JJ: Enter a description for the first commit.
449Add file1 & file2
450
451JJ: This commit contains the following changes:
452JJ: A file1
453
454JJ: Lines starting with "JJ: " (like this one) will be removed.
455"#
456 );
457 assert_eq!(
458 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(),
459 r#"JJ: Enter a description for the second commit.
460Add file1 & file2
461
462JJ: This commit contains the following changes:
463JJ: A file2
464
465JJ: Lines starting with "JJ: " (like this one) will be removed.
466"#
467 );
468}
469
470// This test makes sure that the children of the commit being split retain any
471// other parents which weren't involved in the split.
472#[test]
473fn test_split_siblings_with_merge_child() {
474 let mut test_env = TestEnvironment::default();
475 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
476 let workspace_path = test_env.env_root().join("repo");
477 test_env.jj_cmd_ok(&workspace_path, &["describe", "-m=1"]);
478 test_env.jj_cmd_ok(&workspace_path, &["new", "root()", "-m=a"]);
479 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap();
480 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap();
481 test_env.jj_cmd_ok(
482 &workspace_path,
483 &["new", "description(1)", "description(a)", "-m=2"],
484 );
485 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
486 @ zsuskulnrvyr true 2
487 ├─╮
488 │ ◉ kkmpptxzrspx false a
489 ◉ │ qpvuntsmwlqt true 1
490 ├─╯
491 ◉ zzzzzzzzzzzz true
492 "###);
493
494 // Set up the editor and do the split.
495 let edit_script = test_env.set_up_fake_editor();
496 std::fs::write(
497 edit_script,
498 ["write\nAdd file1", "next invocation\n", "write\nAdd file2"].join("\0"),
499 )
500 .unwrap();
501 let (stdout, stderr) = test_env.jj_cmd_ok(
502 &workspace_path,
503 &["split", "-r", "description(a)", "--siblings", "file1"],
504 );
505 insta::assert_snapshot!(stdout, @"");
506 insta::assert_snapshot!(stderr, @r###"
507 Rebased 1 descendant commits
508 First part: kkmpptxz e8006b47 Add file1
509 Second part: royxmykx 2cc60f3d Add file2
510 Working copy now at: zsuskuln 2f04d1d1 (empty) 2
511 Parent commit : qpvuntsm dc0e5d61 (empty) 1
512 Parent commit : kkmpptxz e8006b47 Add file1
513 Parent commit : royxmykx 2cc60f3d Add file2
514 "###);
515 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###"
516 @ zsuskulnrvyr true 2
517 ├─┬─╮
518 │ │ ◉ royxmykxtrkr false Add file2
519 │ ◉ │ kkmpptxzrspx false Add file1
520 │ ├─╯
521 ◉ │ qpvuntsmwlqt true 1
522 ├─╯
523 ◉ zzzzzzzzzzzz true
524 "###);
525}