diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 95f01fd7c0..0862bc0996 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -1017,9 +1017,20 @@ impl WorkspaceCommandHelper { let mut iter = [commit0, commit1].into_iter().chain(iter); let commits: Vec<_> = iter.by_ref().take(5).try_collect()?; let elided = iter.next().is_some(); - let hint = if let RevsetExpression::CommitRef(RevsetCommitRef::Symbol( - branch_name, - )) = revset_expression.as_ref() + let hint = if commits[0].change_id() == commits[1].change_id() { + // Separate hint if there's commits with same change id + format!( + r#"The revset "{revision_str}" resolved to these revisions:{eol}{commits}{ellipsis} +These commits have the same change id. Abandon one of them with "jj abandon -r "."#, + eol = "\n", + commits = commits + .iter() + .map(|c| self.format_commit_summary(c)) + .join("\n"), + ellipsis = elided.then_some("\n...").unwrap_or_default() + ) + } else if let RevsetExpression::CommitRef(RevsetCommitRef::Symbol(branch_name)) = + revset_expression.as_ref() { // Separate hint if there's a conflicted branch format!( diff --git a/cli/tests/test_checkout.rs b/cli/tests/test_checkout.rs index 664872c177..8002828f79 100644 --- a/cli/tests/test_checkout.rs +++ b/cli/tests/test_checkout.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::common::TestEnvironment; @@ -100,6 +100,38 @@ fn test_checkout_not_single_rev() { "###); } +#[test] +fn test_checkout_conflicting_change_ids() { + let (test_env, repo_path) = set_up(); + + test_env.jj_cmd_success(&repo_path, &["branch", "create", "conflict"]); + test_env.jj_cmd_success(&repo_path, &["describe", "--message", "foo"]); + test_env.jj_cmd_success(&repo_path, &["git", "push", "--branch", "conflict"]); + test_env.jj_cmd_success(&repo_path, &["describe", "--message", "bar"]); + test_env.jj_cmd_success(&repo_path, &["git", "fetch"]); + test_env.jj_cmd_success(&repo_path, &["checkout", "conflict@origin"]); + + let stdout = test_env.jj_cmd_success(&repo_path, &["log"]); + insta::assert_snapshot!(stdout, @r###" + @ wqnwkozp test.user@example.com 2001-02-03 04:05:19.000 +07:00 434ac141 + │ (empty) (no description set) + ◉ yqosqzyt?? test.user@example.com 2001-02-03 04:05:15.000 +07:00 conflict@origin 6553d57b + │ (empty) foo + │ ◉ yqosqzyt?? test.user@example.com 2001-02-03 04:05:17.000 +07:00 conflict* 8d2be885 + ├─╯ (empty) bar + ◉ zzzzzzzz root() 00000000 + "###); + + let stderr = test_env.jj_cmd_failure(&repo_path, &["checkout", "yqosqzyt"]); + insta::assert_snapshot!(stderr, @r###" + Error: Revset "yqosqzyt" resolved to more than one revision + Hint: The revset "yqosqzyt" resolved to these revisions: + yqosqzyt 8d2be885 conflict* | (empty) bar + yqosqzyt 6553d57b conflict@origin | (empty) foo + These commits have the same change id. Abandon one of them with "jj abandon -r ". + "###); +} + #[test] fn test_checkout_conflicting_branches() { // Much of this test is borrowed from `test_git_fetch_conflicting_branches` in @@ -111,7 +143,17 @@ fn test_checkout_conflicting_branches() { test_env.jj_cmd_success(&repo_path, &["describe", "-m", "one"]); test_env.jj_cmd_success(&repo_path, &["new", "-m", "two", "@-"]); test_env.jj_cmd_success(&repo_path, &["branch", "create", "foo"]); - test_env.jj_cmd_success(&repo_path, &["--at-op=@-", "branch", "create", "foo", "-r", r#"description("one")"#]); + test_env.jj_cmd_success( + &repo_path, + &[ + "--at-op=@-", + "branch", + "create", + "foo", + "-r", + r#"description("one")"#, + ], + ); let stdout = test_env.jj_cmd_success(&repo_path, &["log"]); insta::assert_snapshot!(stdout, @r###" @@ -138,3 +180,32 @@ fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String { let template = r#"commit_id ++ " " ++ description"#; test_env.jj_cmd_success(cwd, &["log", "-T", template]) } + +fn set_up() -> (TestEnvironment, PathBuf) { + let test_env = TestEnvironment::default(); + test_env.jj_cmd_success(test_env.env_root(), &["init", "--git", "origin"]); + let origin_path = test_env.env_root().join("origin"); + let origin_git_repo_path = origin_path + .join(".jj") + .join("repo") + .join("store") + .join("git"); + + test_env.jj_cmd_success(&origin_path, &["describe", "-m=description 1"]); + test_env.jj_cmd_success(&origin_path, &["branch", "create", "branch1"]); + test_env.jj_cmd_success(&origin_path, &["new", "root()", "-m=description 2"]); + test_env.jj_cmd_success(&origin_path, &["branch", "create", "branch2"]); + test_env.jj_cmd_success(&origin_path, &["git", "export"]); + + test_env.jj_cmd_success( + test_env.env_root(), + &[ + "git", + "clone", + origin_git_repo_path.to_str().unwrap(), + "local", + ], + ); + let workspace_root = test_env.env_root().join("local"); + (test_env, workspace_root) +}