just playing with tangled
1// Copyright 2024 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
15#[cfg(unix)]
16use std::os::unix::fs::PermissionsExt as _;
17use std::path::PathBuf;
18
19use indoc::formatdoc;
20use indoc::indoc;
21use jj_lib::file_util::try_symlink;
22
23use crate::common::to_toml_value;
24use crate::common::TestEnvironment;
25
26fn set_up_fake_formatter(test_env: &TestEnvironment, args: &[&str]) {
27 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
28 assert!(formatter_path.is_file());
29 test_env.add_config(formatdoc! {"
30 [fix.tools.fake-formatter]
31 command = {command}
32 patterns = ['all()']
33 ",
34 command = toml_edit::Value::from_iter(
35 [formatter_path.to_str().unwrap()]
36 .iter()
37 .chain(args)
38 .copied()
39 )
40 });
41}
42
43#[test]
44fn test_config_no_tools() {
45 let test_env = TestEnvironment::default();
46 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
47 let work_dir = test_env.work_dir("repo");
48
49 work_dir.write_file("file", "content\n");
50 let output = work_dir.run_jj(["fix"]);
51 insta::assert_snapshot!(output, @r"
52 ------- stderr -------
53 Config error: No `fix.tools` are configured
54 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
55 [EOF]
56 [exit status: 1]
57 ");
58
59 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
60 insta::assert_snapshot!(output, @r"
61 content
62 [EOF]
63 ");
64}
65
66#[test]
67fn test_config_multiple_tools() {
68 let test_env = TestEnvironment::default();
69 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
70 let work_dir = test_env.work_dir("repo");
71 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
72 assert!(formatter_path.is_file());
73 let formatter = to_toml_value(formatter_path.to_str().unwrap());
74 test_env.add_config(format!(
75 r###"
76 [fix.tools.tool-1]
77 command = [{formatter}, "--uppercase"]
78 patterns = ["foo"]
79
80 [fix.tools.tool-2]
81 command = [{formatter}, "--lowercase"]
82 patterns = ["bar"]
83 "###,
84 ));
85
86 work_dir.write_file("foo", "Foo\n");
87 work_dir.write_file("bar", "Bar\n");
88 work_dir.write_file("baz", "Baz\n");
89
90 work_dir.run_jj(["fix"]).success();
91
92 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
93 insta::assert_snapshot!(output, @r"
94 FOO
95 [EOF]
96 ");
97 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
98 insta::assert_snapshot!(output, @r"
99 bar
100 [EOF]
101 ");
102 let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]);
103 insta::assert_snapshot!(output, @r"
104 Baz
105 [EOF]
106 ");
107}
108
109#[test]
110fn test_config_multiple_tools_with_same_name() {
111 let mut test_env = TestEnvironment::default();
112 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
113 let work_dir = test_env.work_dir("repo");
114 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
115 assert!(formatter_path.is_file());
116 let formatter = to_toml_value(formatter_path.to_str().unwrap());
117
118 // Multiple definitions with the same `name` are not allowed, because it is
119 // likely to be a mistake, and mistakes are risky when they rewrite files.
120 test_env.add_config(format!(
121 r###"
122 [fix.tools.my-tool]
123 command = [{formatter}, "--uppercase"]
124 patterns = ["foo"]
125
126 [fix.tools.my-tool]
127 command = [{formatter}, "--lowercase"]
128 patterns = ["bar"]
129 "###,
130 ));
131
132 work_dir.write_file("foo", "Foo\n");
133 work_dir.write_file("bar", "Bar\n");
134
135 let output = work_dir.run_jj(["fix"]);
136 insta::assert_snapshot!(output, @r"
137 ------- stderr -------
138 Config error: Configuration cannot be parsed as TOML document
139 Caused by: TOML parse error at line 6, column 9
140 |
141 6 | [fix.tools.my-tool]
142 | ^
143 invalid table header
144 duplicate key `my-tool` in table `fix.tools`
145
146 Hint: Check the config file: $TEST_ENV/config/config0002.toml
147 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
148 [EOF]
149 [exit status: 1]
150 ");
151
152 test_env.set_config_path("/dev/null");
153 let work_dir = test_env.work_dir("repo");
154 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
155 insta::assert_snapshot!(output, @r"
156 Foo
157 [EOF]
158 ");
159 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
160 insta::assert_snapshot!(output, @r"
161 Bar
162 [EOF]
163 ");
164}
165
166#[test]
167fn test_config_disabled_tools() {
168 let test_env = TestEnvironment::default();
169 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
170 let work_dir = test_env.work_dir("repo");
171 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
172 assert!(formatter_path.is_file());
173 let formatter = to_toml_value(formatter_path.to_str().unwrap());
174 test_env.add_config(format!(
175 r###"
176 [fix.tools.tool-1]
177 # default is enabled
178 command = [{formatter}, "--uppercase"]
179 patterns = ["foo"]
180
181 [fix.tools.tool-2]
182 enabled = true
183 command = [{formatter}, "--lowercase"]
184 patterns = ["bar"]
185
186 [fix.tools.tool-3]
187 enabled = false
188 command = [{formatter}, "--lowercase"]
189 patterns = ["baz"]
190 "###
191 ));
192
193 work_dir.write_file("foo", "Foo\n");
194 work_dir.write_file("bar", "Bar\n");
195 work_dir.write_file("baz", "Baz\n");
196
197 work_dir.run_jj(["fix"]).success();
198
199 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
200 insta::assert_snapshot!(output, @r"
201 FOO
202 [EOF]
203 ");
204 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
205 insta::assert_snapshot!(output, @r"
206 bar
207 [EOF]
208 ");
209 let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]);
210 insta::assert_snapshot!(output, @r"
211 Baz
212 [EOF]
213 ");
214}
215
216#[test]
217fn test_config_disabled_tools_warning_when_all_tools_are_disabled() {
218 let test_env = TestEnvironment::default();
219 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
220 let work_dir = test_env.work_dir("repo");
221 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
222 assert!(formatter_path.is_file());
223 let formatter = to_toml_value(formatter_path.to_str().unwrap());
224 test_env.add_config(format!(
225 r###"
226 [fix.tools.tool-2]
227 enabled = false
228 command = [{formatter}, "--lowercase"]
229 patterns = ["bar"]
230 "###
231 ));
232
233 work_dir.write_file("bar", "Bar\n");
234
235 let output = work_dir.run_jj(["fix"]);
236 insta::assert_snapshot!(output, @r"
237 ------- stderr -------
238 Config error: At least one entry of `fix.tools` must be enabled.
239 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
240 [EOF]
241 [exit status: 1]
242 ");
243}
244
245#[test]
246fn test_config_tables_overlapping_patterns() {
247 let test_env = TestEnvironment::default();
248 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
249 let work_dir = test_env.work_dir("repo");
250 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
251 assert!(formatter_path.is_file());
252 let formatter = to_toml_value(formatter_path.to_str().unwrap());
253
254 test_env.add_config(format!(
255 r###"
256 [fix.tools.tool-1]
257 command = [{formatter}, "--append", "tool-1"]
258 patterns = ["foo", "bar"]
259
260 [fix.tools.tool-2]
261 command = [{formatter}, "--append", "tool-2"]
262 patterns = ["bar", "baz"]
263 "###,
264 ));
265
266 work_dir.write_file("foo", "foo\n");
267 work_dir.write_file("bar", "bar\n");
268 work_dir.write_file("baz", "baz\n");
269
270 work_dir.run_jj(["fix"]).success();
271
272 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
273 insta::assert_snapshot!(output, @r"
274 foo
275 tool-1[EOF]
276 ");
277 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
278 insta::assert_snapshot!(output, @r"
279 bar
280 tool-1
281 tool-2[EOF]
282 ");
283 let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]);
284 insta::assert_snapshot!(output, @r"
285 baz
286 tool-2[EOF]
287 ");
288}
289
290#[test]
291fn test_config_tables_all_commands_missing() {
292 let test_env = TestEnvironment::default();
293 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
294 let work_dir = test_env.work_dir("repo");
295 test_env.add_config(
296 r###"
297 [fix.tools.my-tool-missing-command-1]
298 patterns = ["foo"]
299
300 [fix.tools.my-tool-missing-command-2]
301 patterns = ['glob:"ba*"']
302 "###,
303 );
304
305 work_dir.write_file("foo", "foo\n");
306
307 let output = work_dir.run_jj(["fix"]);
308 insta::assert_snapshot!(output.normalize_backslash(), @r"
309 ------- stderr -------
310 Config error: Invalid type or value for fix.tools.my-tool-missing-command-1
311 Caused by: missing field `command`
312
313 Hint: Check the config file: $TEST_ENV/config/config0002.toml
314 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
315 [EOF]
316 [exit status: 1]
317 ");
318
319 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
320 insta::assert_snapshot!(output, @r"
321 foo
322 [EOF]
323 ");
324}
325
326#[test]
327fn test_config_tables_some_commands_missing() {
328 let test_env = TestEnvironment::default();
329 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
330 let work_dir = test_env.work_dir("repo");
331 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
332 assert!(formatter_path.is_file());
333 let formatter = to_toml_value(formatter_path.to_str().unwrap());
334 test_env.add_config(format!(
335 r###"
336 [fix.tools.tool-1]
337 command = [{formatter}, "--uppercase"]
338 patterns = ["foo"]
339
340 [fix.tools.my-tool-missing-command]
341 patterns = ['bar']
342 "###,
343 ));
344
345 work_dir.write_file("foo", "foo\n");
346
347 let output = work_dir.run_jj(["fix"]);
348 insta::assert_snapshot!(output.normalize_backslash(), @r"
349 ------- stderr -------
350 Config error: Invalid type or value for fix.tools.my-tool-missing-command
351 Caused by: missing field `command`
352
353 Hint: Check the config file: $TEST_ENV/config/config0002.toml
354 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
355 [EOF]
356 [exit status: 1]
357 ");
358
359 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
360 insta::assert_snapshot!(output, @r"
361 foo
362 [EOF]
363 ");
364}
365
366#[test]
367fn test_config_tables_empty_patterns_list() {
368 let test_env = TestEnvironment::default();
369 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
370 let work_dir = test_env.work_dir("repo");
371 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
372 assert!(formatter_path.is_file());
373 let formatter = to_toml_value(formatter_path.to_str().unwrap());
374 test_env.add_config(format!(
375 r###"
376 [fix.tools.my-tool-empty-patterns]
377 command = [{formatter}, "--uppercase"]
378 patterns = []
379 "###,
380 ));
381
382 work_dir.write_file("foo", "foo\n");
383
384 let output = work_dir.run_jj(["fix"]);
385 insta::assert_snapshot!(output, @r"
386 ------- stderr -------
387 Fixed 0 commits of 1 checked.
388 Nothing changed.
389 [EOF]
390 ");
391
392 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
393 insta::assert_snapshot!(output, @r"
394 foo
395 [EOF]
396 ");
397}
398
399#[test]
400fn test_config_filesets() {
401 let test_env = TestEnvironment::default();
402 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
403 let work_dir = test_env.work_dir("repo");
404 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
405 assert!(formatter_path.is_file());
406 let formatter = to_toml_value(formatter_path.to_str().unwrap());
407 test_env.add_config(format!(
408 r###"
409 [fix.tools.my-tool-match-one]
410 command = [{formatter}, "--uppercase"]
411 patterns = ['glob:"a*"']
412
413 [fix.tools.my-tool-match-two]
414 command = [{formatter}, "--reverse"]
415 patterns = ['glob:"b*"']
416
417 [fix.tools.my-tool-match-none]
418 command = [{formatter}, "--append", "SHOULD NOT APPEAR"]
419 patterns = ['glob:"this-doesnt-match-anything-*"']
420 "###,
421 ));
422
423 work_dir.write_file("a1", "a1\n");
424 work_dir.write_file("b1", "b1\n");
425 work_dir.write_file("b2", "b2\n");
426
427 work_dir.run_jj(["fix"]).success();
428
429 let output = work_dir.run_jj(["file", "show", "a1", "-r", "@"]);
430 insta::assert_snapshot!(output, @r"
431 A1
432 [EOF]
433 ");
434 let output = work_dir.run_jj(["file", "show", "b1", "-r", "@"]);
435 insta::assert_snapshot!(output, @r"
436 1b
437 [EOF]
438 ");
439 let output = work_dir.run_jj(["file", "show", "b2", "-r", "@"]);
440 insta::assert_snapshot!(output, @r"
441 2b
442 [EOF]
443 ");
444}
445
446#[test]
447fn test_relative_paths() {
448 let test_env = TestEnvironment::default();
449 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
450 let work_dir = test_env.work_dir("repo");
451 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
452 assert!(formatter_path.is_file());
453 let formatter = to_toml_value(formatter_path.to_str().unwrap());
454 test_env.add_config(format!(
455 r###"
456 [fix.tools.tool]
457 command = [{formatter}, "--stdout", "Fixed!"]
458 patterns = ['glob:"foo*"']
459 "###,
460 ));
461
462 let sub_dir = work_dir.create_dir("dir");
463 work_dir.write_file("foo1", "unfixed\n");
464 work_dir.write_file("foo2", "unfixed\n");
465 work_dir.write_file("dir/foo3", "unfixed\n");
466
467 // Positional arguments are cwd-relative, but the configured patterns are
468 // repo-relative, so this command fixes the empty intersection of those
469 // filesets.
470 sub_dir.run_jj(["fix", "foo3"]).success();
471 let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]);
472 insta::assert_snapshot!(output, @r"
473 unfixed
474 [EOF]
475 ");
476 let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]);
477 insta::assert_snapshot!(output, @r"
478 unfixed
479 [EOF]
480 ");
481 let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]);
482 insta::assert_snapshot!(output, @r"
483 unfixed
484 [EOF]
485 ");
486
487 // Positional arguments can specify a subset of the configured fileset.
488 sub_dir.run_jj(["fix", "../foo1"]).success();
489 let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]);
490 insta::assert_snapshot!(output, @"Fixed![EOF]");
491 let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]);
492 insta::assert_snapshot!(output, @r"
493 unfixed
494 [EOF]
495 ");
496 let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]);
497 insta::assert_snapshot!(output, @r"
498 unfixed
499 [EOF]
500 ");
501
502 // The current directory does not change the interpretation of the config, so
503 // foo2 is fixed but not dir/foo3.
504 sub_dir.run_jj(["fix"]).success();
505 let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]);
506 insta::assert_snapshot!(output, @"Fixed![EOF]");
507 let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]);
508 insta::assert_snapshot!(output, @"Fixed![EOF]");
509 let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]);
510 insta::assert_snapshot!(output, @r"
511 unfixed
512 [EOF]
513 ");
514}
515
516#[test]
517fn test_fix_empty_commit() {
518 let test_env = TestEnvironment::default();
519 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
520 let work_dir = test_env.work_dir("repo");
521 set_up_fake_formatter(&test_env, &["--uppercase"]);
522 let output = work_dir.run_jj(["fix", "-s", "@"]);
523 insta::assert_snapshot!(output, @r"
524 ------- stderr -------
525 Fixed 0 commits of 1 checked.
526 Nothing changed.
527 [EOF]
528 ");
529}
530
531#[test]
532fn test_fix_leaf_commit() {
533 let test_env = TestEnvironment::default();
534 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
535 let work_dir = test_env.work_dir("repo");
536 set_up_fake_formatter(&test_env, &["--uppercase"]);
537 work_dir.write_file("file", "unaffected");
538 work_dir.run_jj(["new"]).success();
539 work_dir.write_file("file", "affected");
540
541 let output = work_dir.run_jj(["fix", "-s", "@"]);
542 insta::assert_snapshot!(output, @r"
543 ------- stderr -------
544 Fixed 1 commits of 1 checked.
545 Working copy (@) now at: rlvkpnrz 85ce8924 (no description set)
546 Parent commit (@-) : qpvuntsm b2ca2bc5 (no description set)
547 Added 0 files, modified 1 files, removed 0 files
548 [EOF]
549 ");
550 let output = work_dir.run_jj(["file", "show", "file", "-r", "@-"]);
551 insta::assert_snapshot!(output, @"unaffected[EOF]");
552 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
553 insta::assert_snapshot!(output, @r"
554 AFFECTED
555 [EOF]
556 ");
557}
558
559#[test]
560fn test_fix_parent_commit() {
561 let test_env = TestEnvironment::default();
562 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
563 let work_dir = test_env.work_dir("repo");
564 set_up_fake_formatter(&test_env, &["--uppercase"]);
565 // Using one file name for all commits adds coverage of some possible bugs.
566 work_dir.write_file("file", "parent");
567 work_dir
568 .run_jj(["bookmark", "create", "-r@", "parent"])
569 .success();
570 work_dir.run_jj(["new"]).success();
571 work_dir.write_file("file", "child1");
572 work_dir
573 .run_jj(["bookmark", "create", "-r@", "child1"])
574 .success();
575 work_dir.run_jj(["new", "-r", "parent"]).success();
576 work_dir.write_file("file", "child2");
577 work_dir
578 .run_jj(["bookmark", "create", "-r@", "child2"])
579 .success();
580
581 let output = work_dir.run_jj(["fix", "-s", "parent"]);
582 insta::assert_snapshot!(output, @r"
583 ------- stderr -------
584 Fixed 3 commits of 3 checked.
585 Working copy (@) now at: mzvwutvl d30c8ae2 child2 | (no description set)
586 Parent commit (@-) : qpvuntsm 70a4dae2 parent | (no description set)
587 Added 0 files, modified 1 files, removed 0 files
588 [EOF]
589 ");
590 let output = work_dir.run_jj(["file", "show", "file", "-r", "parent"]);
591 insta::assert_snapshot!(output, @r"
592 PARENT
593 [EOF]
594 ");
595 let output = work_dir.run_jj(["file", "show", "file", "-r", "child1"]);
596 insta::assert_snapshot!(output, @r"
597 CHILD1
598 [EOF]
599 ");
600 let output = work_dir.run_jj(["file", "show", "file", "-r", "child2"]);
601 insta::assert_snapshot!(output, @r"
602 CHILD2
603 [EOF]
604 ");
605}
606
607#[test]
608fn test_fix_sibling_commit() {
609 let test_env = TestEnvironment::default();
610 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
611 let work_dir = test_env.work_dir("repo");
612 set_up_fake_formatter(&test_env, &["--uppercase"]);
613 work_dir.write_file("file", "parent");
614 work_dir
615 .run_jj(["bookmark", "create", "-r@", "parent"])
616 .success();
617 work_dir.run_jj(["new"]).success();
618 work_dir.write_file("file", "child1");
619 work_dir
620 .run_jj(["bookmark", "create", "-r@", "child1"])
621 .success();
622 work_dir.run_jj(["new", "-r", "parent"]).success();
623 work_dir.write_file("file", "child2");
624 work_dir
625 .run_jj(["bookmark", "create", "-r@", "child2"])
626 .success();
627
628 let output = work_dir.run_jj(["fix", "-s", "child1"]);
629 insta::assert_snapshot!(output, @r"
630 ------- stderr -------
631 Fixed 1 commits of 1 checked.
632 [EOF]
633 ");
634 let output = work_dir.run_jj(["file", "show", "file", "-r", "parent"]);
635 insta::assert_snapshot!(output, @"parent[EOF]");
636 let output = work_dir.run_jj(["file", "show", "file", "-r", "child1"]);
637 insta::assert_snapshot!(output, @r"
638 CHILD1
639 [EOF]
640 ");
641 let output = work_dir.run_jj(["file", "show", "file", "-r", "child2"]);
642 insta::assert_snapshot!(output, @"child2[EOF]");
643}
644
645#[test]
646fn test_default_revset() {
647 let test_env = TestEnvironment::default();
648 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
649 let work_dir = test_env.work_dir("repo");
650 set_up_fake_formatter(&test_env, &["--uppercase"]);
651 work_dir.write_file("file", "trunk1");
652 work_dir
653 .run_jj(["bookmark", "create", "-r@", "trunk1"])
654 .success();
655 work_dir.run_jj(["new"]).success();
656 work_dir.write_file("file", "trunk2");
657 work_dir
658 .run_jj(["bookmark", "create", "-r@", "trunk2"])
659 .success();
660 work_dir.run_jj(["new", "trunk1"]).success();
661 work_dir.write_file("file", "foo");
662 work_dir
663 .run_jj(["bookmark", "create", "-r@", "foo"])
664 .success();
665 work_dir.run_jj(["new", "trunk1"]).success();
666 work_dir.write_file("file", "bar1");
667 work_dir
668 .run_jj(["bookmark", "create", "-r@", "bar1"])
669 .success();
670 work_dir.run_jj(["new"]).success();
671 work_dir.write_file("file", "bar2");
672 work_dir
673 .run_jj(["bookmark", "create", "-r@", "bar2"])
674 .success();
675 work_dir.run_jj(["new"]).success();
676 work_dir.write_file("file", "bar3");
677 work_dir
678 .run_jj(["bookmark", "create", "-r@", "bar3"])
679 .success();
680 work_dir.run_jj(["edit", "bar2"]).success();
681
682 // With no args and no revset configuration, we fix `reachable(@, mutable())`,
683 // which includes bar{1,2,3} and excludes trunk{1,2} (which is immutable) and
684 // foo (which is mutable but not reachable).
685 test_env.add_config(r#"revset-aliases."immutable_heads()" = "trunk2""#);
686 let output = work_dir.run_jj(["fix"]);
687 insta::assert_snapshot!(output, @r"
688 ------- stderr -------
689 Fixed 3 commits of 3 checked.
690 Working copy (@) now at: yostqsxw dabc47b2 bar2 | (no description set)
691 Parent commit (@-) : yqosqzyt 984b5924 bar1 | (no description set)
692 Added 0 files, modified 1 files, removed 0 files
693 [EOF]
694 ");
695 let output = work_dir.run_jj(["file", "show", "file", "-r", "trunk1"]);
696 insta::assert_snapshot!(output, @"trunk1[EOF]");
697 let output = work_dir.run_jj(["file", "show", "file", "-r", "trunk2"]);
698 insta::assert_snapshot!(output, @"trunk2[EOF]");
699 let output = work_dir.run_jj(["file", "show", "file", "-r", "foo"]);
700 insta::assert_snapshot!(output, @"foo[EOF]");
701 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar1"]);
702 insta::assert_snapshot!(output, @r"
703 BAR1
704 [EOF]
705 ");
706 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar2"]);
707 insta::assert_snapshot!(output, @r"
708 BAR2
709 [EOF]
710 ");
711 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar3"]);
712 insta::assert_snapshot!(output, @r"
713 BAR3
714 [EOF]
715 ");
716}
717
718#[test]
719fn test_custom_default_revset() {
720 let test_env = TestEnvironment::default();
721 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
722 let work_dir = test_env.work_dir("repo");
723 set_up_fake_formatter(&test_env, &["--uppercase"]);
724
725 work_dir.write_file("file", "foo");
726 work_dir
727 .run_jj(["bookmark", "create", "-r@", "foo"])
728 .success();
729 work_dir.run_jj(["new"]).success();
730 work_dir.write_file("file", "bar");
731 work_dir
732 .run_jj(["bookmark", "create", "-r@", "bar"])
733 .success();
734
735 // Check out a different commit so that the schema default `reachable(@,
736 // mutable())` would behave differently from our customized default.
737 work_dir.run_jj(["new", "-r", "foo"]).success();
738 test_env.add_config(r#"revsets.fix = "bar""#);
739
740 let output = work_dir.run_jj(["fix"]);
741 insta::assert_snapshot!(output, @r"
742 ------- stderr -------
743 Fixed 1 commits of 1 checked.
744 [EOF]
745 ");
746 let output = work_dir.run_jj(["file", "show", "file", "-r", "foo"]);
747 insta::assert_snapshot!(output, @"foo[EOF]");
748 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar"]);
749 insta::assert_snapshot!(output, @r"
750 BAR
751 [EOF]
752 ");
753}
754
755#[test]
756fn test_fix_immutable_commit() {
757 let test_env = TestEnvironment::default();
758 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
759 let work_dir = test_env.work_dir("repo");
760 set_up_fake_formatter(&test_env, &["--uppercase"]);
761 work_dir.write_file("file", "immutable");
762 work_dir
763 .run_jj(["bookmark", "create", "-r@", "immutable"])
764 .success();
765 work_dir.run_jj(["new"]).success();
766 work_dir.write_file("file", "mutable");
767 work_dir
768 .run_jj(["bookmark", "create", "-r@", "mutable"])
769 .success();
770 test_env.add_config(r#"revset-aliases."immutable_heads()" = "immutable""#);
771
772 let output = work_dir.run_jj(["fix", "-s", "immutable"]);
773 insta::assert_snapshot!(output, @r#"
774 ------- stderr -------
775 Error: Commit e4b41a3ce243 is immutable
776 Hint: Could not modify commit: qpvuntsm e4b41a3c immutable | (no description set)
777 Hint: Immutable commits are used to protect shared history.
778 Hint: For more information, see:
779 - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits
780 - `jj help -k config`, "Set of immutable commits"
781 Hint: This operation would rewrite 1 immutable commits.
782 [EOF]
783 [exit status: 1]
784 "#);
785 let output = work_dir.run_jj(["file", "show", "file", "-r", "immutable"]);
786 insta::assert_snapshot!(output, @"immutable[EOF]");
787 let output = work_dir.run_jj(["file", "show", "file", "-r", "mutable"]);
788 insta::assert_snapshot!(output, @"mutable[EOF]");
789}
790
791#[test]
792fn test_fix_empty_file() {
793 let test_env = TestEnvironment::default();
794 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
795 let work_dir = test_env.work_dir("repo");
796 set_up_fake_formatter(&test_env, &["--uppercase"]);
797 work_dir.write_file("file", "");
798
799 let output = work_dir.run_jj(["fix", "-s", "@"]);
800 insta::assert_snapshot!(output, @r"
801 ------- stderr -------
802 Fixed 0 commits of 1 checked.
803 Nothing changed.
804 [EOF]
805 ");
806 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
807 insta::assert_snapshot!(output, @"");
808}
809
810#[test]
811fn test_fix_some_paths() {
812 let test_env = TestEnvironment::default();
813 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
814 let work_dir = test_env.work_dir("repo");
815 set_up_fake_formatter(&test_env, &["--uppercase"]);
816 work_dir.write_file("file1", "foo");
817 work_dir.write_file("file2", "bar");
818
819 let output = work_dir.run_jj(["fix", "-s", "@", "file1"]);
820 insta::assert_snapshot!(output, @r"
821 ------- stderr -------
822 Fixed 1 commits of 1 checked.
823 Working copy (@) now at: qpvuntsm 54a90d2b (no description set)
824 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
825 Added 0 files, modified 1 files, removed 0 files
826 [EOF]
827 ");
828 let output = work_dir.run_jj(["file", "show", "file1"]);
829 insta::assert_snapshot!(output, @r"
830 FOO
831 [EOF]
832 ");
833 let output = work_dir.run_jj(["file", "show", "file2"]);
834 insta::assert_snapshot!(output, @"bar[EOF]");
835}
836
837#[test]
838fn test_fix_cyclic() {
839 let test_env = TestEnvironment::default();
840 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
841 let work_dir = test_env.work_dir("repo");
842 set_up_fake_formatter(&test_env, &["--reverse"]);
843 work_dir.write_file("file", "content\n");
844
845 let output = work_dir.run_jj(["fix"]);
846 insta::assert_snapshot!(output, @r"
847 ------- stderr -------
848 Fixed 1 commits of 1 checked.
849 Working copy (@) now at: qpvuntsm bf5e6a5a (no description set)
850 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
851 Added 0 files, modified 1 files, removed 0 files
852 [EOF]
853 ");
854 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
855 insta::assert_snapshot!(output, @r"
856 tnetnoc
857 [EOF]
858 ");
859
860 let output = work_dir.run_jj(["fix"]);
861 insta::assert_snapshot!(output, @r"
862 ------- stderr -------
863 Fixed 1 commits of 1 checked.
864 Working copy (@) now at: qpvuntsm 0e2d20d6 (no description set)
865 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
866 Added 0 files, modified 1 files, removed 0 files
867 [EOF]
868 ");
869 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
870 insta::assert_snapshot!(output, @r"
871 content
872 [EOF]
873 ");
874}
875
876#[test]
877fn test_deduplication() {
878 // Append all fixed content to a log file. Note that fix tools are always run
879 // from the workspace root, so this will always write to $root/$path-fixlog.
880 let test_env = TestEnvironment::default();
881 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
882 let work_dir = test_env.work_dir("repo");
883 set_up_fake_formatter(&test_env, &["--uppercase", "--tee", "$path-fixlog"]);
884
885 // There are at least two interesting cases: the content is repeated immediately
886 // in the child commit, or later in another descendant.
887 work_dir.write_file("file", "foo\n");
888 work_dir
889 .run_jj(["bookmark", "create", "-r@", "a"])
890 .success();
891 work_dir.run_jj(["new"]).success();
892 work_dir.write_file("file", "bar\n");
893 work_dir
894 .run_jj(["bookmark", "create", "-r@", "b"])
895 .success();
896 work_dir.run_jj(["new"]).success();
897 work_dir.write_file("file", "bar\n");
898 work_dir
899 .run_jj(["bookmark", "create", "-r@", "c"])
900 .success();
901 work_dir.run_jj(["new"]).success();
902 work_dir.write_file("file", "foo\n");
903 work_dir
904 .run_jj(["bookmark", "create", "-r@", "d"])
905 .success();
906
907 let output = work_dir.run_jj(["fix", "-s", "a"]);
908 insta::assert_snapshot!(output, @r"
909 ------- stderr -------
910 Fixed 4 commits of 4 checked.
911 Working copy (@) now at: yqosqzyt cf770245 d | (no description set)
912 Parent commit (@-) : mzvwutvl 370615a5 c | (empty) (no description set)
913 Added 0 files, modified 1 files, removed 0 files
914 [EOF]
915 ");
916 let output = work_dir.run_jj(["file", "show", "file", "-r", "a"]);
917 insta::assert_snapshot!(output, @r"
918 FOO
919 [EOF]
920 ");
921 let output = work_dir.run_jj(["file", "show", "file", "-r", "b"]);
922 insta::assert_snapshot!(output, @r"
923 BAR
924 [EOF]
925 ");
926 let output = work_dir.run_jj(["file", "show", "file", "-r", "c"]);
927 insta::assert_snapshot!(output, @r"
928 BAR
929 [EOF]
930 ");
931 let output = work_dir.run_jj(["file", "show", "file", "-r", "d"]);
932 insta::assert_snapshot!(output, @r"
933 FOO
934 [EOF]
935 ");
936
937 // Each new content string only appears once in the log, because all the other
938 // inputs (like file name) were identical, and so the results were reused. We
939 // sort the log because the order of execution inside `jj fix` is undefined.
940 insta::assert_snapshot!(sorted_lines(work_dir.root().join("file-fixlog")), @r"
941 BAR
942 FOO
943 ");
944}
945
946fn sorted_lines(path: PathBuf) -> String {
947 let mut log: Vec<_> = std::fs::read_to_string(path.as_os_str())
948 .unwrap()
949 .lines()
950 .map(String::from)
951 .collect();
952 log.sort();
953 log.join("\n")
954}
955
956#[test]
957fn test_executed_but_nothing_changed() {
958 // Show that the tool ran by causing a side effect with --tee, and test that we
959 // do the right thing when the tool's output is exactly equal to its input.
960 let test_env = TestEnvironment::default();
961 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
962 let work_dir = test_env.work_dir("repo");
963 set_up_fake_formatter(&test_env, &["--tee", "$path-copy"]);
964 work_dir.write_file("file", "content\n");
965
966 let output = work_dir.run_jj(["fix", "-s", "@"]);
967 insta::assert_snapshot!(output, @r"
968 ------- stderr -------
969 Fixed 0 commits of 1 checked.
970 Nothing changed.
971 [EOF]
972 ");
973 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
974 insta::assert_snapshot!(output, @r"
975 content
976 [EOF]
977 ");
978 let copy_content = work_dir.read_file("file-copy");
979 insta::assert_snapshot!(copy_content, @"content");
980
981 // fix tools are always run from the workspace root, regardless of working
982 // directory at time of invocation.
983 let sub_dir = work_dir.create_dir("dir");
984 let output = sub_dir.run_jj(["fix"]);
985 insta::assert_snapshot!(output, @r"
986 ------- stderr -------
987 Fixed 0 commits of 1 checked.
988 Nothing changed.
989 [EOF]
990 ");
991
992 let copy_content = work_dir.read_file("file-copy");
993 insta::assert_snapshot!(copy_content, @r"
994 content
995 content
996 ");
997 assert!(!sub_dir.root().join("file-copy").exists());
998}
999
1000#[test]
1001fn test_failure() {
1002 let test_env = TestEnvironment::default();
1003 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1004 let work_dir = test_env.work_dir("repo");
1005 set_up_fake_formatter(&test_env, &["--fail"]);
1006 work_dir.write_file("file", "content");
1007
1008 let output = work_dir.run_jj(["fix", "-s", "@"]);
1009 insta::assert_snapshot!(output, @r"
1010 ------- stderr -------
1011 Fixed 0 commits of 1 checked.
1012 Nothing changed.
1013 [EOF]
1014 ");
1015 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
1016 insta::assert_snapshot!(output, @"content[EOF]");
1017}
1018
1019#[test]
1020fn test_stderr_success() {
1021 let test_env = TestEnvironment::default();
1022 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1023 let work_dir = test_env.work_dir("repo");
1024 set_up_fake_formatter(&test_env, &["--stderr", "error", "--stdout", "new content"]);
1025 work_dir.write_file("file", "old content");
1026
1027 // TODO: Associate the stderr lines with the relevant tool/file/commit instead
1028 // of passing it through directly.
1029 let output = work_dir.run_jj(["fix", "-s", "@"]);
1030 insta::assert_snapshot!(output, @r"
1031 ------- stderr -------
1032 errorFixed 1 commits of 1 checked.
1033 Working copy (@) now at: qpvuntsm 487808ba (no description set)
1034 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
1035 Added 0 files, modified 1 files, removed 0 files
1036 [EOF]
1037 ");
1038 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
1039 insta::assert_snapshot!(output, @"new content[EOF]");
1040}
1041
1042#[test]
1043fn test_stderr_failure() {
1044 let test_env = TestEnvironment::default();
1045 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1046 let work_dir = test_env.work_dir("repo");
1047 set_up_fake_formatter(
1048 &test_env,
1049 &["--stderr", "error", "--stdout", "new content", "--fail"],
1050 );
1051 work_dir.write_file("file", "old content");
1052
1053 let output = work_dir.run_jj(["fix", "-s", "@"]);
1054 insta::assert_snapshot!(output, @r"
1055 ------- stderr -------
1056 errorFixed 0 commits of 1 checked.
1057 Nothing changed.
1058 [EOF]
1059 ");
1060 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
1061 insta::assert_snapshot!(output, @"old content[EOF]");
1062}
1063
1064#[test]
1065fn test_missing_command() {
1066 let test_env = TestEnvironment::default();
1067 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1068 let work_dir = test_env.work_dir("repo");
1069 test_env.add_config(indoc! {"
1070 [fix.tools.bad-tool]
1071 command = ['this_executable_shouldnt_exist']
1072 patterns = ['all()']
1073 "});
1074 // TODO: We should display a warning about invalid tool configurations. When we
1075 // support multiple tools, we should also keep going to see if any of the other
1076 // executions succeed.
1077 let output = work_dir.run_jj(["fix", "-s", "@"]);
1078 insta::assert_snapshot!(output, @r"
1079 ------- stderr -------
1080 Fixed 0 commits of 1 checked.
1081 Nothing changed.
1082 [EOF]
1083 ");
1084}
1085
1086#[test]
1087fn test_fix_file_types() {
1088 let test_env = TestEnvironment::default();
1089 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1090 let work_dir = test_env.work_dir("repo");
1091 set_up_fake_formatter(&test_env, &["--uppercase"]);
1092 work_dir.write_file("file", "content");
1093 work_dir.create_dir("dir");
1094 try_symlink("file", work_dir.root().join("link")).unwrap();
1095
1096 let output = work_dir.run_jj(["fix", "-s", "@"]);
1097 insta::assert_snapshot!(output, @r"
1098 ------- stderr -------
1099 Fixed 1 commits of 1 checked.
1100 Working copy (@) now at: qpvuntsm 6836a9e4 (no description set)
1101 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
1102 Added 0 files, modified 1 files, removed 0 files
1103 [EOF]
1104 ");
1105 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
1106 insta::assert_snapshot!(output, @r"
1107 CONTENT
1108 [EOF]
1109 ");
1110}
1111
1112#[cfg(unix)]
1113#[test]
1114fn test_fix_executable() {
1115 let test_env = TestEnvironment::default();
1116 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1117 let work_dir = test_env.work_dir("repo");
1118 set_up_fake_formatter(&test_env, &["--uppercase"]);
1119 let path = work_dir.root().join("file");
1120 work_dir.write_file("file", "content");
1121 let mut permissions = std::fs::metadata(&path).unwrap().permissions();
1122 permissions.set_mode(permissions.mode() | 0o111);
1123 std::fs::set_permissions(&path, permissions).unwrap();
1124
1125 let output = work_dir.run_jj(["fix", "-s", "@"]);
1126 insta::assert_snapshot!(output, @r"
1127 ------- stderr -------
1128 Fixed 1 commits of 1 checked.
1129 Working copy (@) now at: qpvuntsm fee78e99 (no description set)
1130 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
1131 Added 0 files, modified 1 files, removed 0 files
1132 [EOF]
1133 ");
1134 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
1135 insta::assert_snapshot!(output, @r"
1136 CONTENT
1137 [EOF]
1138 ");
1139 let executable = std::fs::metadata(&path).unwrap().permissions().mode() & 0o111;
1140 assert_eq!(executable, 0o111);
1141}
1142
1143#[test]
1144fn test_fix_trivial_merge_commit() {
1145 // All the changes are attributable to a parent, so none are fixed (in the same
1146 // way that none would be shown in `jj diff -r @`).
1147 let test_env = TestEnvironment::default();
1148 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1149 let work_dir = test_env.work_dir("repo");
1150 set_up_fake_formatter(&test_env, &["--uppercase"]);
1151 work_dir.write_file("file_a", "content a");
1152 work_dir.write_file("file_c", "content c");
1153 work_dir
1154 .run_jj(["bookmark", "create", "-r@", "a"])
1155 .success();
1156 work_dir.run_jj(["new", "@-"]).success();
1157 work_dir.write_file("file_b", "content b");
1158 work_dir.write_file("file_c", "content c");
1159 work_dir
1160 .run_jj(["bookmark", "create", "-r@", "b"])
1161 .success();
1162 work_dir.run_jj(["new", "a", "b"]).success();
1163
1164 let output = work_dir.run_jj(["fix", "-s", "@"]);
1165 insta::assert_snapshot!(output, @r"
1166 ------- stderr -------
1167 Fixed 0 commits of 1 checked.
1168 Nothing changed.
1169 [EOF]
1170 ");
1171 let output = work_dir.run_jj(["file", "show", "file_a", "-r", "@"]);
1172 insta::assert_snapshot!(output, @"content a[EOF]");
1173 let output = work_dir.run_jj(["file", "show", "file_b", "-r", "@"]);
1174 insta::assert_snapshot!(output, @"content b[EOF]");
1175 let output = work_dir.run_jj(["file", "show", "file_c", "-r", "@"]);
1176 insta::assert_snapshot!(output, @"content c[EOF]");
1177}
1178
1179#[test]
1180fn test_fix_adding_merge_commit() {
1181 // None of the changes are attributable to a parent, so they are all fixed (in
1182 // the same way that they would be shown in `jj diff -r @`).
1183 let test_env = TestEnvironment::default();
1184 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1185 let work_dir = test_env.work_dir("repo");
1186 set_up_fake_formatter(&test_env, &["--uppercase"]);
1187 work_dir.write_file("file_a", "content a");
1188 work_dir.write_file("file_c", "content c");
1189 work_dir
1190 .run_jj(["bookmark", "create", "-r@", "a"])
1191 .success();
1192 work_dir.run_jj(["new", "@-"]).success();
1193 work_dir.write_file("file_b", "content b");
1194 work_dir.write_file("file_c", "content c");
1195 work_dir
1196 .run_jj(["bookmark", "create", "-r@", "b"])
1197 .success();
1198 work_dir.run_jj(["new", "a", "b"]).success();
1199 work_dir.write_file("file_a", "change a");
1200 work_dir.write_file("file_b", "change b");
1201 work_dir.write_file("file_c", "change c");
1202 work_dir.write_file("file_d", "change d");
1203
1204 let output = work_dir.run_jj(["fix", "-s", "@"]);
1205 insta::assert_snapshot!(output, @r"
1206 ------- stderr -------
1207 Fixed 1 commits of 1 checked.
1208 Working copy (@) now at: mzvwutvl f93eb5a9 (no description set)
1209 Parent commit (@-) : qpvuntsm 6e64e7a7 a | (no description set)
1210 Parent commit (@-) : kkmpptxz c536f264 b | (no description set)
1211 Added 0 files, modified 4 files, removed 0 files
1212 [EOF]
1213 ");
1214 let output = work_dir.run_jj(["file", "show", "file_a", "-r", "@"]);
1215 insta::assert_snapshot!(output, @r"
1216 CHANGE A
1217 [EOF]
1218 ");
1219 let output = work_dir.run_jj(["file", "show", "file_b", "-r", "@"]);
1220 insta::assert_snapshot!(output, @r"
1221 CHANGE B
1222 [EOF]
1223 ");
1224 let output = work_dir.run_jj(["file", "show", "file_c", "-r", "@"]);
1225 insta::assert_snapshot!(output, @r"
1226 CHANGE C
1227 [EOF]
1228 ");
1229 let output = work_dir.run_jj(["file", "show", "file_d", "-r", "@"]);
1230 insta::assert_snapshot!(output, @r"
1231 CHANGE D
1232 [EOF]
1233 ");
1234}
1235
1236#[test]
1237fn test_fix_both_sides_of_conflict() {
1238 let test_env = TestEnvironment::default();
1239 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1240 let work_dir = test_env.work_dir("repo");
1241 set_up_fake_formatter(&test_env, &["--uppercase"]);
1242 work_dir.write_file("file", "content a\n");
1243 work_dir
1244 .run_jj(["bookmark", "create", "-r@", "a"])
1245 .success();
1246 work_dir.run_jj(["new", "@-"]).success();
1247 work_dir.write_file("file", "content b\n");
1248 work_dir
1249 .run_jj(["bookmark", "create", "-r@", "b"])
1250 .success();
1251 work_dir.run_jj(["new", "a", "b"]).success();
1252
1253 // The conflicts are not different from the merged parent, so they would not be
1254 // fixed if we didn't fix the parents also.
1255 let output = work_dir.run_jj(["fix", "-s", "a", "-s", "b"]);
1256 insta::assert_snapshot!(output, @r"
1257 ------- stderr -------
1258 Fixed 3 commits of 3 checked.
1259 Working copy (@) now at: mzvwutvl a55c6ec2 (conflict) (empty) (no description set)
1260 Parent commit (@-) : qpvuntsm 8e8aad69 a | (no description set)
1261 Parent commit (@-) : kkmpptxz 91f9b284 b | (no description set)
1262 Added 0 files, modified 1 files, removed 0 files
1263 Warning: There are unresolved conflicts at these paths:
1264 file 2-sided conflict
1265 [EOF]
1266 ");
1267 let output = work_dir.run_jj(["file", "show", "file", "-r", "a"]);
1268 insta::assert_snapshot!(output, @r"
1269 CONTENT A
1270 [EOF]
1271 ");
1272 let output = work_dir.run_jj(["file", "show", "file", "-r", "b"]);
1273 insta::assert_snapshot!(output, @r"
1274 CONTENT B
1275 [EOF]
1276 ");
1277 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
1278 insta::assert_snapshot!(output, @r"
1279 <<<<<<< Conflict 1 of 1
1280 %%%%%%% Changes from base to side #1
1281 +CONTENT A
1282 +++++++ Contents of side #2
1283 CONTENT B
1284 >>>>>>> Conflict 1 of 1 ends
1285 [EOF]
1286 ");
1287}
1288
1289#[test]
1290fn test_fix_resolve_conflict() {
1291 // If both sides of the conflict look the same after being fixed, the conflict
1292 // will be resolved.
1293 let test_env = TestEnvironment::default();
1294 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1295 let work_dir = test_env.work_dir("repo");
1296 set_up_fake_formatter(&test_env, &["--uppercase"]);
1297 work_dir.write_file("file", "Content\n");
1298 work_dir
1299 .run_jj(["bookmark", "create", "-r@", "a"])
1300 .success();
1301 work_dir.run_jj(["new", "@-"]).success();
1302 work_dir.write_file("file", "cOnTeNt\n");
1303 work_dir
1304 .run_jj(["bookmark", "create", "-r@", "b"])
1305 .success();
1306 work_dir.run_jj(["new", "a", "b"]).success();
1307
1308 // The conflicts are not different from the merged parent, so they would not be
1309 // fixed if we didn't fix the parents also.
1310 let output = work_dir.run_jj(["fix", "-s", "a", "-s", "b"]);
1311 insta::assert_snapshot!(output, @r"
1312 ------- stderr -------
1313 Fixed 3 commits of 3 checked.
1314 Working copy (@) now at: mzvwutvl 50fd048d (empty) (no description set)
1315 Parent commit (@-) : qpvuntsm dd2721f1 a | (no description set)
1316 Parent commit (@-) : kkmpptxz 07c27a8e b | (no description set)
1317 Added 0 files, modified 1 files, removed 0 files
1318 [EOF]
1319 ");
1320 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
1321 insta::assert_snapshot!(output, @r"
1322 CONTENT
1323 [EOF]
1324 ");
1325}
1326
1327#[test]
1328fn test_all_files() {
1329 let test_env = TestEnvironment::default();
1330 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1331 let work_dir = test_env.work_dir("repo");
1332 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
1333 assert!(formatter_path.is_file());
1334 let formatter = to_toml_value(formatter_path.to_str().unwrap());
1335
1336 // Consider a few cases:
1337 // File A: in patterns, changed in child
1338 // File B: in patterns, NOT changed in child
1339 // File C: NOT in patterns, NOT changed in child
1340 // File D: NOT in patterns, changed in child
1341 // Some files will be in subdirectories to make sure we're covering that aspect
1342 // of matching.
1343 test_env.add_config(format!(
1344 r###"
1345 [fix.tools.tool]
1346 command = [{formatter}, "--append", "fixed"]
1347 patterns = ["a/a", "b/b"]
1348 "###,
1349 ));
1350
1351 work_dir.create_dir("a");
1352 work_dir.create_dir("b");
1353 work_dir.create_dir("c");
1354 work_dir.write_file("a/a", "parent aaa\n");
1355 work_dir.write_file("b/b", "parent bbb\n");
1356 work_dir.write_file("c/c", "parent ccc\n");
1357 work_dir.write_file("ddd", "parent ddd\n");
1358 work_dir.run_jj(["commit", "-m", "parent"]).success();
1359
1360 work_dir.write_file("a/a", "child aaa\n");
1361 work_dir.write_file("ddd", "child ddd\n");
1362 work_dir.run_jj(["describe", "-m", "child"]).success();
1363
1364 // Specifying files means exactly those files will be fixed in each revision,
1365 // although some like file C won't have any tools configured to make changes to
1366 // them. Specified but unfixed files are silently skipped, whether they lack
1367 // configuration, are ignored, don't exist, aren't normal files, etc.
1368 let output = work_dir.run_jj([
1369 "fix",
1370 "--include-unchanged-files",
1371 "b/b",
1372 "c/c",
1373 "does_not.exist",
1374 ]);
1375 insta::assert_snapshot!(output, @r"
1376 ------- stderr -------
1377 Fixed 2 commits of 2 checked.
1378 Working copy (@) now at: rlvkpnrz c098d165 child
1379 Parent commit (@-) : qpvuntsm 0bb31627 parent
1380 Added 0 files, modified 1 files, removed 0 files
1381 [EOF]
1382 ");
1383
1384 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@-"]);
1385 insta::assert_snapshot!(output, @r"
1386 parent aaa
1387 [EOF]
1388 ");
1389 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@-"]);
1390 insta::assert_snapshot!(output, @r"
1391 parent bbb
1392 fixed[EOF]
1393 ");
1394 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@-"]);
1395 insta::assert_snapshot!(output, @r"
1396 parent ccc
1397 [EOF]
1398 ");
1399 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@-"]);
1400 insta::assert_snapshot!(output, @r"
1401 parent ddd
1402 [EOF]
1403 ");
1404
1405 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@"]);
1406 insta::assert_snapshot!(output, @r"
1407 child aaa
1408 [EOF]
1409 ");
1410 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@"]);
1411 insta::assert_snapshot!(output, @r"
1412 parent bbb
1413 fixed[EOF]
1414 ");
1415 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@"]);
1416 insta::assert_snapshot!(output, @r"
1417 parent ccc
1418 [EOF]
1419 ");
1420 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@"]);
1421 insta::assert_snapshot!(output, @r"
1422 child ddd
1423 [EOF]
1424 ");
1425
1426 // Not specifying files means all files will be fixed in each revision.
1427 let output = work_dir.run_jj(["fix", "--include-unchanged-files"]);
1428 insta::assert_snapshot!(output, @r"
1429 ------- stderr -------
1430 Fixed 2 commits of 2 checked.
1431 Working copy (@) now at: rlvkpnrz c5d0aa1d child
1432 Parent commit (@-) : qpvuntsm b4d02ca9 parent
1433 Added 0 files, modified 2 files, removed 0 files
1434 [EOF]
1435 ");
1436
1437 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@-"]);
1438 insta::assert_snapshot!(output, @r"
1439 parent aaa
1440 fixed[EOF]
1441 ");
1442 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@-"]);
1443 insta::assert_snapshot!(output, @r"
1444 parent bbb
1445 fixed
1446 fixed[EOF]
1447 ");
1448 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@-"]);
1449 insta::assert_snapshot!(output, @r"
1450 parent ccc
1451 [EOF]
1452 ");
1453 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@-"]);
1454 insta::assert_snapshot!(output, @r"
1455 parent ddd
1456 [EOF]
1457 ");
1458
1459 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@"]);
1460 insta::assert_snapshot!(output, @r"
1461 child aaa
1462 fixed[EOF]
1463 ");
1464 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@"]);
1465 insta::assert_snapshot!(output, @r"
1466 parent bbb
1467 fixed
1468 fixed[EOF]
1469 ");
1470 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@"]);
1471 insta::assert_snapshot!(output, @r"
1472 parent ccc
1473 [EOF]
1474 ");
1475 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@"]);
1476 insta::assert_snapshot!(output, @r"
1477 child ddd
1478 [EOF]
1479 ");
1480}