// 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;use std::path::PathBuf; use itertools::Itertools;use jj_lib::file_util::try_symlink; use crate::common::TestEnvironment; /// Set up a repo where the `jj fix` command uses the fake editor with the given/// flags. Returns a function that redacts the formatter executable's path from/// a given string for test determinism.fn init_with_fake_formatter(args: &[&str]) -> (TestEnvironment, PathBuf, impl Fn(&str) -> String) { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); // The deprecated configuration syntax is still used by tests where it doesn't // make a meaningful difference in coverage. Otherwise, we would have to add // dedicated test coverage for the deprecated syntax until it is removed. We use // single quotes here to avoid escaping issues when running the test on Windows. test_env.add_config(&format!( r#"fix.tool-command = ['{}']"#, [formatter_path.to_str().unwrap()] .iter() .chain(args) .join(r#"', '"#) )); (test_env, repo_path, move |snapshot: &str| { // When the test runs on Windows, backslashes in the path complicate things by // changing the double quotes to single quotes in the serialized TOML. snapshot.replace( &if cfg!(windows) { format!(r#"'{}'"#, formatter_path.to_str().unwrap()) } else { format!(r#""{}""#, formatter_path.to_str().unwrap()) }, "<redacted formatter path>", ) })} #[test]fn test_config_no_tools() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); std::fs::write(repo_path.join("file"), "content\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); insta::assert_snapshot!(stderr, @r###" Config error: At least one entry of `fix.tools` or `fix.tool-command` is required. For help, see https://martinvonz.github.io/jj/latest/config/. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "@"]); insta::assert_snapshot!(content, @"content\n");} #[test]fn test_config_both_legacy_and_table_tools() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix] tool-command = ["{formatter}", "--append", "legacy change"] [fix.tools.tool-1] command = ["{formatter}", "--append", "tables change"] patterns = ["tables-file"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("legacy-file"), "legacy content\n").unwrap(); std::fs::write(repo_path.join("tables-file"), "tables content\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "legacy-file", "-r", "@"]); insta::assert_snapshot!(content, @r###" legacy content legacy change "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "tables-file", "-r", "@"]); insta::assert_snapshot!(content, @r###" tables content legacy change tables change "###);} #[test]fn test_config_multiple_tools() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.tool-1] command = ["{formatter}", "--uppercase"] patterns = ["foo"] [fix.tools.tool-2] command = ["{formatter}", "--lowercase"] patterns = ["bar"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "Foo\n").unwrap(); std::fs::write(repo_path.join("bar"), "Bar\n").unwrap(); std::fs::write(repo_path.join("baz"), "Baz\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"FOO\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "bar", "-r", "@"]); insta::assert_snapshot!(content, @"bar\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "baz", "-r", "@"]); insta::assert_snapshot!(content, @"Baz\n");} #[test]fn test_config_multiple_tools_with_same_name() { let mut test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); // 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"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "Foo\n").unwrap(); std::fs::write(repo_path.join("bar"), "Bar\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); #[cfg(unix)] insta::assert_snapshot!(stderr, @r###" Config error: redefinition of table `fix.tools.my-tool` for key `fix.tools.my-tool` at line 6 column 9 in ../config/config0002.toml For help, see https://martinvonz.github.io/jj/latest/config/. "###); #[cfg(windows)] insta::assert_snapshot!(stderr, @r###" Config error: redefinition of table `fix.tools.my-tool` for key `fix.tools.my-tool` at line 6 column 9 in ..\config\config0002.toml For help, see https://martinvonz.github.io/jj/latest/config/. "###); test_env.set_config_path("/dev/null".into()); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"Foo\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "bar", "-r", "@"]); insta::assert_snapshot!(content, @"Bar\n");} #[test]fn test_config_tables_overlapping_patterns() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); 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"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); std::fs::write(repo_path.join("bar"), "bar\n").unwrap(); std::fs::write(repo_path.join("baz"), "baz\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @r###" foo tool-1 "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "bar", "-r", "@"]); insta::assert_snapshot!(content, @r###" bar tool-1 tool-2 "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "baz", "-r", "@"]); insta::assert_snapshot!(content, @r###" baz tool-2 "###);} #[test]fn test_config_tables_all_commands_missing() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("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*"'] "###, ); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); insta::assert_snapshot!(stderr, @r###" Config error: missing field `command` For help, see https://martinvonz.github.io/jj/latest/config/. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"foo\n");} #[test]fn test_config_tables_some_commands_missing() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.tool-1] command = ["{formatter}", "--uppercase"] patterns = ["foo"] [fix.tools.my-tool-missing-command] patterns = ['bar'] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); insta::assert_snapshot!(stderr, @r###" Config error: missing field `command` For help, see https://martinvonz.github.io/jj/latest/config/. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"foo\n");} #[test]fn test_config_tables_empty_patterns_list() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.my-tool-empty-patterns] command = ["{formatter}", "--uppercase"] patterns = [] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Fixed 0 commits of 1 checked. Nothing changed. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"foo\n");} #[test]fn test_config_filesets() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); 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-*"'] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("a1"), "a1\n").unwrap(); std::fs::write(repo_path.join("b1"), "b1\n").unwrap(); std::fs::write(repo_path.join("b2"), "b2\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "a1", "-r", "@"]); insta::assert_snapshot!(content, @"A1\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "b1", "-r", "@"]); insta::assert_snapshot!(content, @"1b\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "b2", "-r", "@"]); insta::assert_snapshot!(content, @"2b\n");} #[test]fn test_relative_paths() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.tool] command = ["{formatter}", "--stdout", "Fixed!"] patterns = ['glob:"foo*"'] "###, formatter = escaped_formatter_path.as_str() )); std::fs::create_dir(repo_path.join("dir")).unwrap(); std::fs::write(repo_path.join("foo1"), "unfixed\n").unwrap(); std::fs::write(repo_path.join("foo2"), "unfixed\n").unwrap(); std::fs::write(repo_path.join("dir/foo3"), "unfixed\n").unwrap(); // Positional arguments are cwd-relative, but the configured patterns are // repo-relative, so this command fixes the empty intersection of those // filesets. let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path.join("dir"), &["fix", "foo3"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo1", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo2", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "dir/foo3", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); // Positional arguments can specify a subset of the configured fileset. let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path.join("dir"), &["fix", "../foo1"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo1", "-r", "@"]); insta::assert_snapshot!(content, @"Fixed!\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo2", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "dir/foo3", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); // The current directory does not change the interpretation of the config, so // foo2 is fixed but not dir/foo3. let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path.join("dir"), &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo1", "-r", "@"]); insta::assert_snapshot!(content, @"Fixed!\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo2", "-r", "@"]); insta::assert_snapshot!(content, @"Fixed!\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "dir/foo3", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n");} #[test]fn test_fix_empty_commit() { let (test_env, repo_path, redact) = init_with_fake_formatter(&["--uppercase"]); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix", "-s", "@"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(redact(&stderr), @r###" Warning: The `fix.tool-command` config option is deprecated and will be removed in a future version. Hint: Replace it with the following: [fix.tools.legacy-tool-command] command = [<redacted formatter path>, "--uppercase"] patterns = ["all()"] Fixed 0 commits of 1 checked. Nothing changed. "###);} #[test]fn test_fix_leaf_commit() { let (test_env, repo_path, redact) = init_with_fake_formatter(&["--uppercase"]); std::fs::write(repo_path.join("file"), "unaffected").unwrap(); test_env.jj_cmd_ok(&repo_path, &["new"]); std::fs::write(repo_path.join("file"), "affected").unwrap(); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix", "-s", "@"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(redact(&stderr), @r###" Warning: The `fix.tool-command` config option is deprecated and will be removed in a future version. Hint: Replace it with the following: [fix.tools.legacy-tool-command] command = [<redacted formatter path>, "--uppercase"] patterns = ["all()"] Fixed 1 commits of 1 checked. Working copy now at: rlvkpnrz 85ce8924 (no description set) Parent commit : qpvuntsm b2ca2bc5 (no description set) Added 0 files, modified 1 files, removed 0 files "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "@-"]); insta::assert_snapshot!(content, @"unaffected"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "@"]); insta::assert_snapshot!(content, @"AFFECTED");} #[test]fn test_fix_parent_commit() { let (test_env, repo_path, redact) = init_with_fake_formatter(&["--uppercase"]); // Using one file name for all commits adds coverage of some possible bugs. std::fs::write(repo_path.join("file"), "parent").unwrap(); test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "parent"]); test_env.jj_cmd_ok(&repo_path, &["new"]); std::fs::write(repo_path.join("file"), "child1").unwrap(); test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "child1"]); test_env.jj_cmd_ok(&repo_path, &["new", "-r", "parent"]); std::fs::write(repo_path.join("file"), "child2").unwrap(); test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "child2"]); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix", "-s", "parent"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(redact(&stderr), @r###" Warning: The `fix.tool-command` config option is deprecated and will be removed in a future version. Hint: Replace it with the following: [fix.tools.legacy-tool-command] command = [<redacted formatter path>, "--uppercase"] patterns = ["all()"] Fixed 3 commits of 3 checked. Working copy now at: mzvwutvl d30c8ae2 child2 | (no description set) Parent commit : qpvuntsm 70a4dae2 parent | (no description set) Added 0 files, modified 1 files, removed 0 files "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "parent"]); insta::assert_snapshot!(content, @"PARENT"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "child1"]); insta::assert_snapshot!(content, @"CHILD1"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "child2"]); insta::assert_snapshot!(content, @"CHILD2");} Lines 1–500 of 1273. Load more
// 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; use std::path::PathBuf; use itertools::Itertools; use jj_lib::file_util::try_symlink; use crate::common::TestEnvironment; /// Set up a repo where the `jj fix` command uses the fake editor with the given /// flags. Returns a function that redacts the formatter executable's path from /// a given string for test determinism. fn init_with_fake_formatter(args: &[&str]) -> (TestEnvironment, PathBuf, impl Fn(&str) -> String) { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); // The deprecated configuration syntax is still used by tests where it doesn't // make a meaningful difference in coverage. Otherwise, we would have to add // dedicated test coverage for the deprecated syntax until it is removed. We use // single quotes here to avoid escaping issues when running the test on Windows. test_env.add_config(&format!( r#"fix.tool-command = ['{}']"#, [formatter_path.to_str().unwrap()] .iter() .chain(args) .join(r#"', '"#) )); (test_env, repo_path, move |snapshot: &str| { // When the test runs on Windows, backslashes in the path complicate things by // changing the double quotes to single quotes in the serialized TOML. snapshot.replace( &if cfg!(windows) { format!(r#"'{}'"#, formatter_path.to_str().unwrap()) } else { format!(r#""{}""#, formatter_path.to_str().unwrap()) }, "<redacted formatter path>", ) }) } #[test] fn test_config_no_tools() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); std::fs::write(repo_path.join("file"), "content\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); insta::assert_snapshot!(stderr, @r###" Config error: At least one entry of `fix.tools` or `fix.tool-command` is required. For help, see https://martinvonz.github.io/jj/latest/config/. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "@"]); insta::assert_snapshot!(content, @"content\n"); } #[test] fn test_config_both_legacy_and_table_tools() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix] tool-command = ["{formatter}", "--append", "legacy change"] [fix.tools.tool-1] command = ["{formatter}", "--append", "tables change"] patterns = ["tables-file"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("legacy-file"), "legacy content\n").unwrap(); std::fs::write(repo_path.join("tables-file"), "tables content\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "legacy-file", "-r", "@"]); insta::assert_snapshot!(content, @r###" legacy content legacy change "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "tables-file", "-r", "@"]); insta::assert_snapshot!(content, @r###" tables content legacy change tables change "###); } #[test] fn test_config_multiple_tools() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.tool-1] command = ["{formatter}", "--uppercase"] patterns = ["foo"] [fix.tools.tool-2] command = ["{formatter}", "--lowercase"] patterns = ["bar"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "Foo\n").unwrap(); std::fs::write(repo_path.join("bar"), "Bar\n").unwrap(); std::fs::write(repo_path.join("baz"), "Baz\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"FOO\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "bar", "-r", "@"]); insta::assert_snapshot!(content, @"bar\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "baz", "-r", "@"]); insta::assert_snapshot!(content, @"Baz\n"); } #[test] fn test_config_multiple_tools_with_same_name() { let mut test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); // 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"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "Foo\n").unwrap(); std::fs::write(repo_path.join("bar"), "Bar\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); #[cfg(unix)] insta::assert_snapshot!(stderr, @r###" Config error: redefinition of table `fix.tools.my-tool` for key `fix.tools.my-tool` at line 6 column 9 in ../config/config0002.toml For help, see https://martinvonz.github.io/jj/latest/config/. "###); #[cfg(windows)] insta::assert_snapshot!(stderr, @r###" Config error: redefinition of table `fix.tools.my-tool` for key `fix.tools.my-tool` at line 6 column 9 in ..\config\config0002.toml For help, see https://martinvonz.github.io/jj/latest/config/. "###); test_env.set_config_path("/dev/null".into()); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"Foo\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "bar", "-r", "@"]); insta::assert_snapshot!(content, @"Bar\n"); } #[test] fn test_config_tables_overlapping_patterns() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); 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"] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); std::fs::write(repo_path.join("bar"), "bar\n").unwrap(); std::fs::write(repo_path.join("baz"), "baz\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @r###" foo tool-1 "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "bar", "-r", "@"]); insta::assert_snapshot!(content, @r###" bar tool-1 tool-2 "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "baz", "-r", "@"]); insta::assert_snapshot!(content, @r###" baz tool-2 "###); } #[test] fn test_config_tables_all_commands_missing() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("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*"'] "###, ); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); insta::assert_snapshot!(stderr, @r###" Config error: missing field `command` For help, see https://martinvonz.github.io/jj/latest/config/. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"foo\n"); } #[test] fn test_config_tables_some_commands_missing() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.tool-1] command = ["{formatter}", "--uppercase"] patterns = ["foo"] [fix.tools.my-tool-missing-command] patterns = ['bar'] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); let stderr = test_env.jj_cmd_failure(&repo_path, &["fix"]); insta::assert_snapshot!(stderr, @r###" Config error: missing field `command` For help, see https://martinvonz.github.io/jj/latest/config/. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"foo\n"); } #[test] fn test_config_tables_empty_patterns_list() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.my-tool-empty-patterns] command = ["{formatter}", "--uppercase"] patterns = [] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("foo"), "foo\n").unwrap(); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Fixed 0 commits of 1 checked. Nothing changed. "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo", "-r", "@"]); insta::assert_snapshot!(content, @"foo\n"); } #[test] fn test_config_filesets() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); 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-*"'] "###, formatter = escaped_formatter_path.as_str() )); std::fs::write(repo_path.join("a1"), "a1\n").unwrap(); std::fs::write(repo_path.join("b1"), "b1\n").unwrap(); std::fs::write(repo_path.join("b2"), "b2\n").unwrap(); let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "a1", "-r", "@"]); insta::assert_snapshot!(content, @"A1\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "b1", "-r", "@"]); insta::assert_snapshot!(content, @"1b\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "b2", "-r", "@"]); insta::assert_snapshot!(content, @"2b\n"); } #[test] fn test_relative_paths() { let test_env = TestEnvironment::default(); test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); assert!(formatter_path.is_file()); let escaped_formatter_path = formatter_path.to_str().unwrap().replace('\\', r"\\"); test_env.add_config(&format!( r###" [fix.tools.tool] command = ["{formatter}", "--stdout", "Fixed!"] patterns = ['glob:"foo*"'] "###, formatter = escaped_formatter_path.as_str() )); std::fs::create_dir(repo_path.join("dir")).unwrap(); std::fs::write(repo_path.join("foo1"), "unfixed\n").unwrap(); std::fs::write(repo_path.join("foo2"), "unfixed\n").unwrap(); std::fs::write(repo_path.join("dir/foo3"), "unfixed\n").unwrap(); // Positional arguments are cwd-relative, but the configured patterns are // repo-relative, so this command fixes the empty intersection of those // filesets. let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path.join("dir"), &["fix", "foo3"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo1", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo2", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "dir/foo3", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); // Positional arguments can specify a subset of the configured fileset. let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path.join("dir"), &["fix", "../foo1"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo1", "-r", "@"]); insta::assert_snapshot!(content, @"Fixed!\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo2", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "dir/foo3", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); // The current directory does not change the interpretation of the config, so // foo2 is fixed but not dir/foo3. let (_stdout, _stderr) = test_env.jj_cmd_ok(&repo_path.join("dir"), &["fix"]); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo1", "-r", "@"]); insta::assert_snapshot!(content, @"Fixed!\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "foo2", "-r", "@"]); insta::assert_snapshot!(content, @"Fixed!\n"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "dir/foo3", "-r", "@"]); insta::assert_snapshot!(content, @"unfixed\n"); } #[test] fn test_fix_empty_commit() { let (test_env, repo_path, redact) = init_with_fake_formatter(&["--uppercase"]); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix", "-s", "@"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(redact(&stderr), @r###" Warning: The `fix.tool-command` config option is deprecated and will be removed in a future version. Hint: Replace it with the following: [fix.tools.legacy-tool-command] command = [<redacted formatter path>, "--uppercase"] patterns = ["all()"] Fixed 0 commits of 1 checked. Nothing changed. "###); } #[test] fn test_fix_leaf_commit() { let (test_env, repo_path, redact) = init_with_fake_formatter(&["--uppercase"]); std::fs::write(repo_path.join("file"), "unaffected").unwrap(); test_env.jj_cmd_ok(&repo_path, &["new"]); std::fs::write(repo_path.join("file"), "affected").unwrap(); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix", "-s", "@"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(redact(&stderr), @r###" Warning: The `fix.tool-command` config option is deprecated and will be removed in a future version. Hint: Replace it with the following: [fix.tools.legacy-tool-command] command = [<redacted formatter path>, "--uppercase"] patterns = ["all()"] Fixed 1 commits of 1 checked. Working copy now at: rlvkpnrz 85ce8924 (no description set) Parent commit : qpvuntsm b2ca2bc5 (no description set) Added 0 files, modified 1 files, removed 0 files "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "@-"]); insta::assert_snapshot!(content, @"unaffected"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "@"]); insta::assert_snapshot!(content, @"AFFECTED"); } #[test] fn test_fix_parent_commit() { let (test_env, repo_path, redact) = init_with_fake_formatter(&["--uppercase"]); // Using one file name for all commits adds coverage of some possible bugs. std::fs::write(repo_path.join("file"), "parent").unwrap(); test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "parent"]); test_env.jj_cmd_ok(&repo_path, &["new"]); std::fs::write(repo_path.join("file"), "child1").unwrap(); test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "child1"]); test_env.jj_cmd_ok(&repo_path, &["new", "-r", "parent"]); std::fs::write(repo_path.join("file"), "child2").unwrap(); test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "child2"]); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["fix", "-s", "parent"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(redact(&stderr), @r###" Warning: The `fix.tool-command` config option is deprecated and will be removed in a future version. Hint: Replace it with the following: [fix.tools.legacy-tool-command] command = [<redacted formatter path>, "--uppercase"] patterns = ["all()"] Fixed 3 commits of 3 checked. Working copy now at: mzvwutvl d30c8ae2 child2 | (no description set) Parent commit : qpvuntsm 70a4dae2 parent | (no description set) Added 0 files, modified 1 files, removed 0 files "###); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "parent"]); insta::assert_snapshot!(content, @"PARENT"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "child1"]); insta::assert_snapshot!(content, @"CHILD1"); let content = test_env.jj_cmd_success(&repo_path, &["file", "show", "file", "-r", "child2"]); insta::assert_snapshot!(content, @"CHILD2"); }