jj/cli/tests/test_fix_command.rs
Martin von Zweigbergk acd824269f git: write change-id header by default
We haven't had any reports of problems from people who opted in. Since
it's early in the release cycle now, let's now test it on everyone who
builds from head, so we get almost a month of testing from those
people before it's enabled by default in a released version.

This impacts lots of test cases because the change-id header is added
to the Git commit. Most are uninteresting. `test_git_fetch` now sees
some divergent changes where it used to see only divergent bookmarks,
which makes sense.
2025-05-08 23:05:38 +00:00

1481 lines
49 KiB
Rust

// Copyright 2024 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt as _;
use std::path::PathBuf;
use indoc::formatdoc;
use indoc::indoc;
use jj_lib::file_util::try_symlink;
use crate::common::to_toml_value;
use crate::common::TestEnvironment;
fn set_up_fake_formatter(test_env: &TestEnvironment, args: &[&str]) {
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
test_env.add_config(formatdoc! {"
[fix.tools.fake-formatter]
command = {command}
patterns = ['all()']
",
command = toml_edit::Value::from_iter(
[formatter_path.to_str().unwrap()]
.iter()
.chain(args)
.copied()
)
});
}
#[test]
fn test_config_no_tools() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("file", "content\n");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Config error: No `fix.tools` are configured
For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
content
[EOF]
");
}
#[test]
fn test_config_multiple_tools() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.tool-1]
command = [{formatter}, "--uppercase"]
patterns = ["foo"]
[fix.tools.tool-2]
command = [{formatter}, "--lowercase"]
patterns = ["bar"]
"###,
));
work_dir.write_file("foo", "Foo\n");
work_dir.write_file("bar", "Bar\n");
work_dir.write_file("baz", "Baz\n");
work_dir.run_jj(["fix"]).success();
let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
insta::assert_snapshot!(output, @r"
FOO
[EOF]
");
let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
insta::assert_snapshot!(output, @r"
bar
[EOF]
");
let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]);
insta::assert_snapshot!(output, @r"
Baz
[EOF]
");
}
#[test]
fn test_config_multiple_tools_with_same_name() {
let mut test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
// Multiple definitions with the same `name` are not allowed, because it is
// likely to be a mistake, and mistakes are risky when they rewrite files.
test_env.add_config(format!(
r###"
[fix.tools.my-tool]
command = [{formatter}, "--uppercase"]
patterns = ["foo"]
[fix.tools.my-tool]
command = [{formatter}, "--lowercase"]
patterns = ["bar"]
"###,
));
work_dir.write_file("foo", "Foo\n");
work_dir.write_file("bar", "Bar\n");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Config error: Configuration cannot be parsed as TOML document
Caused by: TOML parse error at line 6, column 9
|
6 | [fix.tools.my-tool]
| ^
invalid table header
duplicate key `my-tool` in table `fix.tools`
Hint: Check the config file: $TEST_ENV/config/config0002.toml
For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
test_env.set_config_path("/dev/null");
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
insta::assert_snapshot!(output, @r"
Foo
[EOF]
");
let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
insta::assert_snapshot!(output, @r"
Bar
[EOF]
");
}
#[test]
fn test_config_disabled_tools() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.tool-1]
# default is enabled
command = [{formatter}, "--uppercase"]
patterns = ["foo"]
[fix.tools.tool-2]
enabled = true
command = [{formatter}, "--lowercase"]
patterns = ["bar"]
[fix.tools.tool-3]
enabled = false
command = [{formatter}, "--lowercase"]
patterns = ["baz"]
"###
));
work_dir.write_file("foo", "Foo\n");
work_dir.write_file("bar", "Bar\n");
work_dir.write_file("baz", "Baz\n");
work_dir.run_jj(["fix"]).success();
let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
insta::assert_snapshot!(output, @r"
FOO
[EOF]
");
let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
insta::assert_snapshot!(output, @r"
bar
[EOF]
");
let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]);
insta::assert_snapshot!(output, @r"
Baz
[EOF]
");
}
#[test]
fn test_config_disabled_tools_warning_when_all_tools_are_disabled() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.tool-2]
enabled = false
command = [{formatter}, "--lowercase"]
patterns = ["bar"]
"###
));
work_dir.write_file("bar", "Bar\n");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Config error: At least one entry of `fix.tools` must be enabled.
For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
}
#[test]
fn test_config_tables_overlapping_patterns() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.tool-1]
command = [{formatter}, "--append", "tool-1"]
patterns = ["foo", "bar"]
[fix.tools.tool-2]
command = [{formatter}, "--append", "tool-2"]
patterns = ["bar", "baz"]
"###,
));
work_dir.write_file("foo", "foo\n");
work_dir.write_file("bar", "bar\n");
work_dir.write_file("baz", "baz\n");
work_dir.run_jj(["fix"]).success();
let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
insta::assert_snapshot!(output, @r"
foo
tool-1[EOF]
");
let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]);
insta::assert_snapshot!(output, @r"
bar
tool-1
tool-2[EOF]
");
let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]);
insta::assert_snapshot!(output, @r"
baz
tool-2[EOF]
");
}
#[test]
fn test_config_tables_all_commands_missing() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
test_env.add_config(
r###"
[fix.tools.my-tool-missing-command-1]
patterns = ["foo"]
[fix.tools.my-tool-missing-command-2]
patterns = ['glob:"ba*"']
"###,
);
work_dir.write_file("foo", "foo\n");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
------- stderr -------
Config error: Invalid type or value for fix.tools.my-tool-missing-command-1
Caused by: missing field `command`
Hint: Check the config file: $TEST_ENV/config/config0002.toml
For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
insta::assert_snapshot!(output, @r"
foo
[EOF]
");
}
#[test]
fn test_config_tables_some_commands_missing() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.tool-1]
command = [{formatter}, "--uppercase"]
patterns = ["foo"]
[fix.tools.my-tool-missing-command]
patterns = ['bar']
"###,
));
work_dir.write_file("foo", "foo\n");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output.normalize_backslash(), @r"
------- stderr -------
Config error: Invalid type or value for fix.tools.my-tool-missing-command
Caused by: missing field `command`
Hint: Check the config file: $TEST_ENV/config/config0002.toml
For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
insta::assert_snapshot!(output, @r"
foo
[EOF]
");
}
#[test]
fn test_config_tables_empty_patterns_list() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.my-tool-empty-patterns]
command = [{formatter}, "--uppercase"]
patterns = []
"###,
));
work_dir.write_file("foo", "foo\n");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]);
insta::assert_snapshot!(output, @r"
foo
[EOF]
");
}
#[test]
fn test_config_filesets() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.my-tool-match-one]
command = [{formatter}, "--uppercase"]
patterns = ['glob:"a*"']
[fix.tools.my-tool-match-two]
command = [{formatter}, "--reverse"]
patterns = ['glob:"b*"']
[fix.tools.my-tool-match-none]
command = [{formatter}, "--append", "SHOULD NOT APPEAR"]
patterns = ['glob:"this-doesnt-match-anything-*"']
"###,
));
work_dir.write_file("a1", "a1\n");
work_dir.write_file("b1", "b1\n");
work_dir.write_file("b2", "b2\n");
work_dir.run_jj(["fix"]).success();
let output = work_dir.run_jj(["file", "show", "a1", "-r", "@"]);
insta::assert_snapshot!(output, @r"
A1
[EOF]
");
let output = work_dir.run_jj(["file", "show", "b1", "-r", "@"]);
insta::assert_snapshot!(output, @r"
1b
[EOF]
");
let output = work_dir.run_jj(["file", "show", "b2", "-r", "@"]);
insta::assert_snapshot!(output, @r"
2b
[EOF]
");
}
#[test]
fn test_relative_paths() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
test_env.add_config(format!(
r###"
[fix.tools.tool]
command = [{formatter}, "--stdout", "Fixed!"]
patterns = ['glob:"foo*"']
"###,
));
let sub_dir = work_dir.create_dir("dir");
work_dir.write_file("foo1", "unfixed\n");
work_dir.write_file("foo2", "unfixed\n");
work_dir.write_file("dir/foo3", "unfixed\n");
// Positional arguments are cwd-relative, but the configured patterns are
// repo-relative, so this command fixes the empty intersection of those
// filesets.
sub_dir.run_jj(["fix", "foo3"]).success();
let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]);
insta::assert_snapshot!(output, @r"
unfixed
[EOF]
");
let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]);
insta::assert_snapshot!(output, @r"
unfixed
[EOF]
");
let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]);
insta::assert_snapshot!(output, @r"
unfixed
[EOF]
");
// Positional arguments can specify a subset of the configured fileset.
sub_dir.run_jj(["fix", "../foo1"]).success();
let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]);
insta::assert_snapshot!(output, @"Fixed![EOF]");
let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]);
insta::assert_snapshot!(output, @r"
unfixed
[EOF]
");
let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]);
insta::assert_snapshot!(output, @r"
unfixed
[EOF]
");
// The current directory does not change the interpretation of the config, so
// foo2 is fixed but not dir/foo3.
sub_dir.run_jj(["fix"]).success();
let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]);
insta::assert_snapshot!(output, @"Fixed![EOF]");
let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]);
insta::assert_snapshot!(output, @"Fixed![EOF]");
let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]);
insta::assert_snapshot!(output, @r"
unfixed
[EOF]
");
}
#[test]
fn test_fix_empty_commit() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
}
#[test]
fn test_fix_leaf_commit() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "unaffected");
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "affected");
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
Working copy (@) now at: rlvkpnrz 45a3ace1 (no description set)
Parent commit (@-) : qpvuntsm b37955c0 (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@-"]);
insta::assert_snapshot!(output, @"unaffected[EOF]");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
AFFECTED
[EOF]
");
}
#[test]
fn test_fix_parent_commit() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
// Using one file name for all commits adds coverage of some possible bugs.
work_dir.write_file("file", "parent");
work_dir
.run_jj(["bookmark", "create", "-r@", "parent"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "child1");
work_dir
.run_jj(["bookmark", "create", "-r@", "child1"])
.success();
work_dir.run_jj(["new", "-r", "parent"]).success();
work_dir.write_file("file", "child2");
work_dir
.run_jj(["bookmark", "create", "-r@", "child2"])
.success();
let output = work_dir.run_jj(["fix", "-s", "parent"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 3 commits of 3 checked.
Working copy (@) now at: mzvwutvl 6842c630 child2 | (no description set)
Parent commit (@-) : qpvuntsm e586ba6c parent | (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "parent"]);
insta::assert_snapshot!(output, @r"
PARENT
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "child1"]);
insta::assert_snapshot!(output, @r"
CHILD1
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "child2"]);
insta::assert_snapshot!(output, @r"
CHILD2
[EOF]
");
}
#[test]
fn test_fix_sibling_commit() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "parent");
work_dir
.run_jj(["bookmark", "create", "-r@", "parent"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "child1");
work_dir
.run_jj(["bookmark", "create", "-r@", "child1"])
.success();
work_dir.run_jj(["new", "-r", "parent"]).success();
work_dir.write_file("file", "child2");
work_dir
.run_jj(["bookmark", "create", "-r@", "child2"])
.success();
let output = work_dir.run_jj(["fix", "-s", "child1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "parent"]);
insta::assert_snapshot!(output, @"parent[EOF]");
let output = work_dir.run_jj(["file", "show", "file", "-r", "child1"]);
insta::assert_snapshot!(output, @r"
CHILD1
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "child2"]);
insta::assert_snapshot!(output, @"child2[EOF]");
}
#[test]
fn test_default_revset() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "trunk1");
work_dir
.run_jj(["bookmark", "create", "-r@", "trunk1"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "trunk2");
work_dir
.run_jj(["bookmark", "create", "-r@", "trunk2"])
.success();
work_dir.run_jj(["new", "trunk1"]).success();
work_dir.write_file("file", "foo");
work_dir
.run_jj(["bookmark", "create", "-r@", "foo"])
.success();
work_dir.run_jj(["new", "trunk1"]).success();
work_dir.write_file("file", "bar1");
work_dir
.run_jj(["bookmark", "create", "-r@", "bar1"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "bar2");
work_dir
.run_jj(["bookmark", "create", "-r@", "bar2"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "bar3");
work_dir
.run_jj(["bookmark", "create", "-r@", "bar3"])
.success();
work_dir.run_jj(["edit", "bar2"]).success();
// With no args and no revset configuration, we fix `reachable(@, mutable())`,
// which includes bar{1,2,3} and excludes trunk{1,2} (which is immutable) and
// foo (which is mutable but not reachable).
test_env.add_config(r#"revset-aliases."immutable_heads()" = "trunk2""#);
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 3 commits of 3 checked.
Working copy (@) now at: yostqsxw 41903dfd bar2 | (no description set)
Parent commit (@-) : yqosqzyt c2e6d322 bar1 | (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "trunk1"]);
insta::assert_snapshot!(output, @"trunk1[EOF]");
let output = work_dir.run_jj(["file", "show", "file", "-r", "trunk2"]);
insta::assert_snapshot!(output, @"trunk2[EOF]");
let output = work_dir.run_jj(["file", "show", "file", "-r", "foo"]);
insta::assert_snapshot!(output, @"foo[EOF]");
let output = work_dir.run_jj(["file", "show", "file", "-r", "bar1"]);
insta::assert_snapshot!(output, @r"
BAR1
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "bar2"]);
insta::assert_snapshot!(output, @r"
BAR2
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "bar3"]);
insta::assert_snapshot!(output, @r"
BAR3
[EOF]
");
}
#[test]
fn test_custom_default_revset() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "foo");
work_dir
.run_jj(["bookmark", "create", "-r@", "foo"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "bar");
work_dir
.run_jj(["bookmark", "create", "-r@", "bar"])
.success();
// Check out a different commit so that the schema default `reachable(@,
// mutable())` would behave differently from our customized default.
work_dir.run_jj(["new", "-r", "foo"]).success();
test_env.add_config(r#"revsets.fix = "bar""#);
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "foo"]);
insta::assert_snapshot!(output, @"foo[EOF]");
let output = work_dir.run_jj(["file", "show", "file", "-r", "bar"]);
insta::assert_snapshot!(output, @r"
BAR
[EOF]
");
}
#[test]
fn test_fix_immutable_commit() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "immutable");
work_dir
.run_jj(["bookmark", "create", "-r@", "immutable"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "mutable");
work_dir
.run_jj(["bookmark", "create", "-r@", "mutable"])
.success();
test_env.add_config(r#"revset-aliases."immutable_heads()" = "immutable""#);
let output = work_dir.run_jj(["fix", "-s", "immutable"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Error: Commit a86b2eccaaab is immutable
Hint: Could not modify commit: qpvuntsm a86b2ecc immutable | (no description set)
Hint: Immutable commits are used to protect shared history.
Hint: For more information, see:
- https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits
- `jj help -k config`, "Set of immutable commits"
Hint: This operation would rewrite 1 immutable commits.
[EOF]
[exit status: 1]
"#);
let output = work_dir.run_jj(["file", "show", "file", "-r", "immutable"]);
insta::assert_snapshot!(output, @"immutable[EOF]");
let output = work_dir.run_jj(["file", "show", "file", "-r", "mutable"]);
insta::assert_snapshot!(output, @"mutable[EOF]");
}
#[test]
fn test_fix_empty_file() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "");
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @"");
}
#[test]
fn test_fix_some_paths() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file1", "foo");
work_dir.write_file("file2", "bar");
let output = work_dir.run_jj(["fix", "-s", "@", "file1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
Working copy (@) now at: qpvuntsm b3e6840d (no description set)
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file1"]);
insta::assert_snapshot!(output, @r"
FOO
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file2"]);
insta::assert_snapshot!(output, @"bar[EOF]");
}
#[test]
fn test_fix_cyclic() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--reverse"]);
work_dir.write_file("file", "content\n");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
Working copy (@) now at: qpvuntsm 087e5fb0 (no description set)
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
tnetnoc
[EOF]
");
let output = work_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
Working copy (@) now at: qpvuntsm 832f43c5 (no description set)
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
content
[EOF]
");
}
#[test]
fn test_deduplication() {
// Append all fixed content to a log file. Note that fix tools are always run
// from the workspace root, so this will always write to $root/$path-fixlog.
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase", "--tee", "$path-fixlog"]);
// There are at least two interesting cases: the content is repeated immediately
// in the child commit, or later in another descendant.
work_dir.write_file("file", "foo\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "a"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "bar\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "b"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "bar\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "c"])
.success();
work_dir.run_jj(["new"]).success();
work_dir.write_file("file", "foo\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "d"])
.success();
let output = work_dir.run_jj(["fix", "-s", "a"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 4 commits of 4 checked.
Working copy (@) now at: yqosqzyt 9849a250 d | (no description set)
Parent commit (@-) : mzvwutvl 9544f381 c | (empty) (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "a"]);
insta::assert_snapshot!(output, @r"
FOO
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "b"]);
insta::assert_snapshot!(output, @r"
BAR
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "c"]);
insta::assert_snapshot!(output, @r"
BAR
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "d"]);
insta::assert_snapshot!(output, @r"
FOO
[EOF]
");
// Each new content string only appears once in the log, because all the other
// inputs (like file name) were identical, and so the results were reused. We
// sort the log because the order of execution inside `jj fix` is undefined.
insta::assert_snapshot!(sorted_lines(work_dir.root().join("file-fixlog")), @r"
BAR
FOO
");
}
fn sorted_lines(path: PathBuf) -> String {
let mut log: Vec<_> = std::fs::read_to_string(path.as_os_str())
.unwrap()
.lines()
.map(String::from)
.collect();
log.sort();
log.join("\n")
}
#[test]
fn test_executed_but_nothing_changed() {
// Show that the tool ran by causing a side effect with --tee, and test that we
// do the right thing when the tool's output is exactly equal to its input.
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--tee", "$path-copy"]);
work_dir.write_file("file", "content\n");
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
content
[EOF]
");
let copy_content = work_dir.read_file("file-copy");
insta::assert_snapshot!(copy_content, @"content");
// fix tools are always run from the workspace root, regardless of working
// directory at time of invocation.
let sub_dir = work_dir.create_dir("dir");
let output = sub_dir.run_jj(["fix"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
let copy_content = work_dir.read_file("file-copy");
insta::assert_snapshot!(copy_content, @r"
content
content
");
assert!(!sub_dir.root().join("file-copy").exists());
}
#[test]
fn test_failure() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--fail"]);
work_dir.write_file("file", "content");
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @"content[EOF]");
}
#[test]
fn test_stderr_success() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--stderr", "error", "--stdout", "new content"]);
work_dir.write_file("file", "old content");
// TODO: Associate the stderr lines with the relevant tool/file/commit instead
// of passing it through directly.
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
errorFixed 1 commits of 1 checked.
Working copy (@) now at: qpvuntsm cb75cbcb (no description set)
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @"new content[EOF]");
}
#[test]
fn test_stderr_failure() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(
&test_env,
&["--stderr", "error", "--stdout", "new content", "--fail"],
);
work_dir.write_file("file", "old content");
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
errorFixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @"old content[EOF]");
}
#[test]
fn test_missing_command() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
test_env.add_config(indoc! {"
[fix.tools.bad-tool]
command = ['this_executable_shouldnt_exist']
patterns = ['all()']
"});
// TODO: We should display a warning about invalid tool configurations. When we
// support multiple tools, we should also keep going to see if any of the other
// executions succeed.
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
}
#[test]
fn test_fix_file_types() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "content");
work_dir.create_dir("dir");
try_symlink("file", work_dir.root().join("link")).unwrap();
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
Working copy (@) now at: qpvuntsm a600eba5 (no description set)
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
CONTENT
[EOF]
");
}
#[cfg(unix)]
#[test]
fn test_fix_executable() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
let path = work_dir.root().join("file");
work_dir.write_file("file", "content");
let mut permissions = std::fs::metadata(&path).unwrap().permissions();
permissions.set_mode(permissions.mode() | 0o111);
std::fs::set_permissions(&path, permissions).unwrap();
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
Working copy (@) now at: qpvuntsm ed402983 (no description set)
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
CONTENT
[EOF]
");
let executable = std::fs::metadata(&path).unwrap().permissions().mode() & 0o111;
assert_eq!(executable, 0o111);
}
#[test]
fn test_fix_trivial_merge_commit() {
// All the changes are attributable to a parent, so none are fixed (in the same
// way that none would be shown in `jj diff -r @`).
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file_a", "content a");
work_dir.write_file("file_c", "content c");
work_dir
.run_jj(["bookmark", "create", "-r@", "a"])
.success();
work_dir.run_jj(["new", "@-"]).success();
work_dir.write_file("file_b", "content b");
work_dir.write_file("file_c", "content c");
work_dir
.run_jj(["bookmark", "create", "-r@", "b"])
.success();
work_dir.run_jj(["new", "a", "b"]).success();
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 0 commits of 1 checked.
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file_a", "-r", "@"]);
insta::assert_snapshot!(output, @"content a[EOF]");
let output = work_dir.run_jj(["file", "show", "file_b", "-r", "@"]);
insta::assert_snapshot!(output, @"content b[EOF]");
let output = work_dir.run_jj(["file", "show", "file_c", "-r", "@"]);
insta::assert_snapshot!(output, @"content c[EOF]");
}
#[test]
fn test_fix_adding_merge_commit() {
// None of the changes are attributable to a parent, so they are all fixed (in
// the same way that they would be shown in `jj diff -r @`).
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file_a", "content a");
work_dir.write_file("file_c", "content c");
work_dir
.run_jj(["bookmark", "create", "-r@", "a"])
.success();
work_dir.run_jj(["new", "@-"]).success();
work_dir.write_file("file_b", "content b");
work_dir.write_file("file_c", "content c");
work_dir
.run_jj(["bookmark", "create", "-r@", "b"])
.success();
work_dir.run_jj(["new", "a", "b"]).success();
work_dir.write_file("file_a", "change a");
work_dir.write_file("file_b", "change b");
work_dir.write_file("file_c", "change c");
work_dir.write_file("file_d", "change d");
let output = work_dir.run_jj(["fix", "-s", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 1 commits of 1 checked.
Working copy (@) now at: mzvwutvl c8d50db3 (no description set)
Parent commit (@-) : qpvuntsm 93f04460 a | (no description set)
Parent commit (@-) : kkmpptxz ad4fc36c b | (no description set)
Added 0 files, modified 4 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file_a", "-r", "@"]);
insta::assert_snapshot!(output, @r"
CHANGE A
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file_b", "-r", "@"]);
insta::assert_snapshot!(output, @r"
CHANGE B
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file_c", "-r", "@"]);
insta::assert_snapshot!(output, @r"
CHANGE C
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file_d", "-r", "@"]);
insta::assert_snapshot!(output, @r"
CHANGE D
[EOF]
");
}
#[test]
fn test_fix_both_sides_of_conflict() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "content a\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "a"])
.success();
work_dir.run_jj(["new", "@-"]).success();
work_dir.write_file("file", "content b\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "b"])
.success();
work_dir.run_jj(["new", "a", "b"]).success();
// The conflicts are not different from the merged parent, so they would not be
// fixed if we didn't fix the parents also.
let output = work_dir.run_jj(["fix", "-s", "a", "-s", "b"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 3 commits of 3 checked.
Working copy (@) now at: mzvwutvl d4d02bf0 (conflict) (empty) (no description set)
Parent commit (@-) : qpvuntsm 0eae0dae a | (no description set)
Parent commit (@-) : kkmpptxz eb61ba8d b | (no description set)
Added 0 files, modified 1 files, removed 0 files
Warning: There are unresolved conflicts at these paths:
file 2-sided conflict
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "a"]);
insta::assert_snapshot!(output, @r"
CONTENT A
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "b"]);
insta::assert_snapshot!(output, @r"
CONTENT B
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
<<<<<<< Conflict 1 of 1
%%%%%%% Changes from base to side #1
+CONTENT A
+++++++ Contents of side #2
CONTENT B
>>>>>>> Conflict 1 of 1 ends
[EOF]
");
}
#[test]
fn test_fix_resolve_conflict() {
// If both sides of the conflict look the same after being fixed, the conflict
// will be resolved.
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
set_up_fake_formatter(&test_env, &["--uppercase"]);
work_dir.write_file("file", "Content\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "a"])
.success();
work_dir.run_jj(["new", "@-"]).success();
work_dir.write_file("file", "cOnTeNt\n");
work_dir
.run_jj(["bookmark", "create", "-r@", "b"])
.success();
work_dir.run_jj(["new", "a", "b"]).success();
// The conflicts are not different from the merged parent, so they would not be
// fixed if we didn't fix the parents also.
let output = work_dir.run_jj(["fix", "-s", "a", "-s", "b"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 3 commits of 3 checked.
Working copy (@) now at: mzvwutvl c4e4665e (empty) (no description set)
Parent commit (@-) : qpvuntsm 7a0dbb95 a | (no description set)
Parent commit (@-) : kkmpptxz 5d9510ab b | (no description set)
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]);
insta::assert_snapshot!(output, @r"
CONTENT
[EOF]
");
}
#[test]
fn test_all_files() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(formatter_path.is_file());
let formatter = to_toml_value(formatter_path.to_str().unwrap());
// Consider a few cases:
// File A: in patterns, changed in child
// File B: in patterns, NOT changed in child
// File C: NOT in patterns, NOT changed in child
// File D: NOT in patterns, changed in child
// Some files will be in subdirectories to make sure we're covering that aspect
// of matching.
test_env.add_config(format!(
r###"
[fix.tools.tool]
command = [{formatter}, "--append", "fixed"]
patterns = ["a/a", "b/b"]
"###,
));
work_dir.create_dir("a");
work_dir.create_dir("b");
work_dir.create_dir("c");
work_dir.write_file("a/a", "parent aaa\n");
work_dir.write_file("b/b", "parent bbb\n");
work_dir.write_file("c/c", "parent ccc\n");
work_dir.write_file("ddd", "parent ddd\n");
work_dir.run_jj(["commit", "-m", "parent"]).success();
work_dir.write_file("a/a", "child aaa\n");
work_dir.write_file("ddd", "child ddd\n");
work_dir.run_jj(["describe", "-m", "child"]).success();
// Specifying files means exactly those files will be fixed in each revision,
// although some like file C won't have any tools configured to make changes to
// them. Specified but unfixed files are silently skipped, whether they lack
// configuration, are ignored, don't exist, aren't normal files, etc.
let output = work_dir.run_jj([
"fix",
"--include-unchanged-files",
"b/b",
"c/c",
"does_not.exist",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 2 commits of 2 checked.
Working copy (@) now at: rlvkpnrz d8503fee child
Parent commit (@-) : qpvuntsm 62c6ee98 parent
Added 0 files, modified 1 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent aaa
[EOF]
");
let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent bbb
fixed[EOF]
");
let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent ccc
[EOF]
");
let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent ddd
[EOF]
");
let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@"]);
insta::assert_snapshot!(output, @r"
child aaa
[EOF]
");
let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@"]);
insta::assert_snapshot!(output, @r"
parent bbb
fixed[EOF]
");
let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@"]);
insta::assert_snapshot!(output, @r"
parent ccc
[EOF]
");
let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@"]);
insta::assert_snapshot!(output, @r"
child ddd
[EOF]
");
// Not specifying files means all files will be fixed in each revision.
let output = work_dir.run_jj(["fix", "--include-unchanged-files"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Fixed 2 commits of 2 checked.
Working copy (@) now at: rlvkpnrz 3675eae3 child
Parent commit (@-) : qpvuntsm 6a0a8a6a parent
Added 0 files, modified 2 files, removed 0 files
[EOF]
");
let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent aaa
fixed[EOF]
");
let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent bbb
fixed
fixed[EOF]
");
let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent ccc
[EOF]
");
let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@-"]);
insta::assert_snapshot!(output, @r"
parent ddd
[EOF]
");
let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@"]);
insta::assert_snapshot!(output, @r"
child aaa
fixed[EOF]
");
let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@"]);
insta::assert_snapshot!(output, @r"
parent bbb
fixed
fixed[EOF]
");
let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@"]);
insta::assert_snapshot!(output, @r"
parent ccc
[EOF]
");
let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@"]);
insta::assert_snapshot!(output, @r"
child ddd
[EOF]
");
}