diff --git a/cli/tests/test_tree_level_conflicts.rs b/cli/tests/test_tree_level_conflicts.rs new file mode 100644 index 000000000..6a0291ffd --- /dev/null +++ b/cli/tests/test_tree_level_conflicts.rs @@ -0,0 +1,85 @@ +// Copyright 2022 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. + +use common::TestEnvironment; + +pub mod common; + +#[test] +fn test_enable_tree_level_conflicts() { + let test_env = TestEnvironment::default(); + test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]); + let repo_path = test_env.env_root().join("repo"); + + // Create a few commits before we enable tree-level conflicts + let file_path = repo_path.join("file"); + test_env.jj_cmd_success(&repo_path, &["new", "root", "-m=left"]); + std::fs::write(&file_path, "left").unwrap(); + test_env.jj_cmd_success(&repo_path, &["new", "root", "-m=right"]); + std::fs::write(&file_path, "right").unwrap(); + test_env.jj_cmd_success( + &repo_path, + &[ + "new", + r#"description("left")"#, + r#"description("right")"#, + "-m=merge", + ], + ); + test_env.jj_cmd_success(&repo_path, &["new"]); + let stdout = test_env.jj_cmd_success(&repo_path, &["log"]); + insta::assert_snapshot!(stdout, @r###" + @ mzvwutvl test.user@example.com 2001-02-03 04:05:11.000 +07:00 f2101bed conflict + │ (empty) (no description set) + ◉ zsuskuln test.user@example.com 2001-02-03 04:05:10.000 +07:00 5100e4e1 conflict + ├─╮ (empty) merge + │ ◉ kkmpptxz test.user@example.com 2001-02-03 04:05:10.000 +07:00 0b65c8fb + │ │ right + ◉ │ rlvkpnrz test.user@example.com 2001-02-03 04:05:09.000 +07:00 32003b88 + ├─╯ left + ◉ zzzzzzzz root 00000000 + "###); + + // Enable tree-level conflicts + test_env.add_config(r#"format.tree-level-conflicts = true"#); + // We get a new working-copy commit. The working copy unfortunately appears + // non-empty + let stdout = test_env.jj_cmd_success(&repo_path, &["log"]); + insta::assert_snapshot!(stdout, @r###" + @ mzvwutvl test.user@example.com 2001-02-03 04:05:13.000 +07:00 54c562fa conflict + │ (no description set) + ◉ zsuskuln test.user@example.com 2001-02-03 04:05:10.000 +07:00 5100e4e1 conflict + ├─╮ (empty) merge + │ ◉ kkmpptxz test.user@example.com 2001-02-03 04:05:10.000 +07:00 0b65c8fb + │ │ right + ◉ │ rlvkpnrz test.user@example.com 2001-02-03 04:05:09.000 +07:00 32003b88 + ├─╯ left + ◉ zzzzzzzz root 00000000 + "###); + // ...but at least it has no diff + let stdout = test_env.jj_cmd_success(&repo_path, &["diff"]); + insta::assert_snapshot!(stdout, @""); + + // If we create new commit off of an unconflicted commit, it also appears + // non-empty + test_env.jj_cmd_success(&repo_path, &["new", "k"]); + let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-r=@"]); + insta::assert_snapshot!(stdout, @r###" + @ yostqsxw test.user@example.com 2001-02-03 04:05:16.000 +07:00 f5e911f1 + │ (no description set) + ~ + "###); + let stdout = test_env.jj_cmd_success(&repo_path, &["diff"]); + insta::assert_snapshot!(stdout, @""); +} diff --git a/lib/src/merged_tree.rs b/lib/src/merged_tree.rs index 13086b3a3..34c16f7ab 100644 --- a/lib/src/merged_tree.rs +++ b/lib/src/merged_tree.rs @@ -940,9 +940,13 @@ impl MergedTreeBuilder { /// Create new tree(s) from the base tree(s) and overrides. /// - /// When the base tree was a legacy tree, then the result will be another - /// legacy tree. Overrides with conflicts will result in conflict objects - /// being written to the store. + /// When the base tree was a legacy tree and the + /// `format.tree-level-conflicts` config is disabled, then the result will + /// be another legacy tree. Overrides with conflicts will result in + /// conflict objects being written to the store. If + /// `format.tree-tree-level-conflicts` is enabled, then a legacy tree will + /// still be written and immediately converted and returned as a merged + /// tree. pub fn write_tree(self, store: &Arc) -> BackendResult { match self.base_tree_id.clone() { MergedTreeId::Legacy(base_tree_id) => { @@ -959,7 +963,14 @@ impl MergedTreeBuilder { } } } - Ok(MergedTreeId::Legacy(tree_builder.write_tree())) + let legacy_id = tree_builder.write_tree(); + if store.use_tree_conflict_format() { + let legacy_tree = store.get_tree(&RepoPath::root(), &legacy_id)?; + let merged_tree = MergedTree::from_legacy_tree(legacy_tree)?; + Ok(merged_tree.id()) + } else { + Ok(MergedTreeId::Legacy(legacy_id)) + } } MergedTreeId::Merge(base_tree_ids) => { let new_tree_ids = self.write_merged_trees(base_tree_ids, store)?; diff --git a/lib/src/repo.rs b/lib/src/repo.rs index 6d477c730..72009eec6 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -152,7 +152,7 @@ impl ReadonlyRepo { let backend = backend_factory(&store_path)?; let backend_path = store_path.join("type"); fs::write(&backend_path, backend.name()).context(&backend_path)?; - let store = Store::new(backend); + let store = Store::new(backend, user_settings.use_tree_conflict_format()); let repo_settings = user_settings.with_repo(&repo_path).unwrap(); let op_store_path = repo_path.join("op_store"); @@ -585,7 +585,10 @@ impl RepoLoader { repo_path: &Path, store_factories: &StoreFactories, ) -> Result { - let store = Store::new(store_factories.load_backend(&repo_path.join("store"))?); + let store = Store::new( + store_factories.load_backend(&repo_path.join("store"))?, + user_settings.use_tree_conflict_format(), + ); let repo_settings = user_settings.with_repo(repo_path).unwrap(); let op_store = Arc::from(store_factories.load_op_store(&repo_path.join("op_store"))?); let op_heads_store = diff --git a/lib/src/settings.rs b/lib/src/settings.rs index 6fafb24bc..1aef61cf2 100644 --- a/lib/src/settings.rs +++ b/lib/src/settings.rs @@ -97,6 +97,12 @@ impl UserSettings { self.rng.clone() } + pub fn use_tree_conflict_format(&self) -> bool { + self.config + .get_bool("format.tree-level-conflicts") + .unwrap_or(false) + } + pub fn user_name(&self) -> String { self.config.get_string("user.name").unwrap_or_default() } diff --git a/lib/src/store.rs b/lib/src/store.rs index 020c21497..34f34512d 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -38,14 +38,16 @@ pub struct Store { backend: Box, commit_cache: RwLock>>, tree_cache: RwLock>>, + use_tree_conflict_format: bool, } impl Store { - pub fn new(backend: Box) -> Arc { + pub fn new(backend: Box, use_tree_conflict_format: bool) -> Arc { Arc::new(Store { backend, commit_cache: Default::default(), tree_cache: Default::default(), + use_tree_conflict_format, }) } @@ -53,6 +55,11 @@ impl Store { self.backend.as_any() } + /// Whether new tree should be written using the tree-level format. + pub fn use_tree_conflict_format(&self) -> bool { + self.use_tree_conflict_format + } + pub fn commit_id_length(&self) -> usize { self.backend.commit_id_length() }