diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index c73ac8e141..95f01fd7c0 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -51,9 +51,9 @@ use jj_lib::repo::{ }; use jj_lib::repo_path::{FsPathParseError, RepoPath}; use jj_lib::revset::{ - DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetEvaluationError, RevsetExpression, - RevsetIteratorExt, RevsetParseContext, RevsetParseError, RevsetParseErrorKind, - RevsetResolutionError, RevsetWorkspaceContext, + DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetCommitRef, RevsetEvaluationError, + RevsetExpression, RevsetIteratorExt, RevsetParseContext, RevsetParseError, + RevsetParseErrorKind, RevsetResolutionError, RevsetWorkspaceContext, }; use jj_lib::settings::{ConfigResultExt as _, UserSettings}; use jj_lib::transaction::Transaction; @@ -1006,7 +1006,7 @@ impl WorkspaceCommandHelper { ui: &mut Ui, ) -> Result { let revset_expression = self.parse_revset(revision_str, Some(ui))?; - let revset = self.evaluate_revset(revset_expression)?; + let revset = self.evaluate_revset(revset_expression.clone())?; let mut iter = revset.iter().commits(self.repo().store()).fuse(); match (iter.next(), iter.next()) { (Some(commit), None) => Ok(commit?), @@ -1017,15 +1017,33 @@ 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 = format!( - r#"The revset "{revision_str}" resolved to these revisions:{eol}{commits}{ellipsis}"#, - eol = "\n", - commits = commits - .iter() - .map(|c| self.format_commit_summary(c)) - .join("\n"), - ellipsis = elided.then_some("\n...").unwrap_or_default() - ); + let hint = if let RevsetExpression::CommitRef(RevsetCommitRef::Symbol( + branch_name, + )) = revset_expression.as_ref() + { + // Separate hint if there's a conflicted branch + format!( + r#"Branch {branch_name} resolved to multiple revisions because it's conflicted. +It resolved to these revisions: +{commits}{ellipsis} +Set which revision the branch points to with "jj branch set {branch_name} -r "."#, + commits = commits + .iter() + .map(|c| self.format_commit_summary(c)) + .join("\n"), + ellipsis = elided.then_some("\n...").unwrap_or_default() + ) + } else { + format!( + r#"The revset "{revision_str}" resolved to these revisions:{eol}{commits}{ellipsis}"#, + eol = "\n", + commits = commits + .iter() + .map(|c| self.format_commit_summary(c)) + .join("\n"), + ellipsis = elided.then_some("\n...").unwrap_or_default() + ) + }; Err(user_error_with_hint( format!(r#"Revset "{revision_str}" resolved to more than one revision"#), hint, diff --git a/cli/tests/test_checkout.rs b/cli/tests/test_checkout.rs index ea1111048a..cae270e31c 100644 --- a/cli/tests/test_checkout.rs +++ b/cli/tests/test_checkout.rs @@ -100,7 +100,80 @@ fn test_checkout_not_single_rev() { "###); } +#[test] +fn test_checkout_conflicting_branches() { + // Much of this test is borrowed from `test_git_fetch_conflicting_branches` in + // test_git_fetch.rs + 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"); + + add_git_remote(&test_env, &repo_path, "rem1"); + + // Create a rem1 branch locally + test_env.jj_cmd_success(&repo_path, &["new", "root()"]); + test_env.jj_cmd_success(&repo_path, &["branch", "create", "rem1"]); + + test_env.jj_cmd_success( + &repo_path, + &["git", "fetch", "--remote", "rem1", "--branch", "*"], + ); + + let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-r", "rem1"]); + insta::assert_snapshot!(stdout, @r###" + ◉ qxosxrvv some.one@example.com 1970-01-01 00:00:00.000 +00:00 rem1?? rem1@rem1 6a211027 + │ message + ~ + + @ kkmpptxz test.user@example.com 2001-02-03 04:05:09.000 +07:00 rem1?? fcdbbd73 + │ (empty) (no description set) + ~ + "###); + + let stderr = test_env.jj_cmd_failure(&repo_path, &["checkout", "rem1"]); + insta::assert_snapshot!(stderr, @r###" + Error: Revset "rem1" resolved to more than one revision + Hint: Branch rem1 resolved to multiple revisions because it's conflicted. + It resolved to these revisions: + qxosxrvv 6a211027 rem1?? rem1@rem1 | message + kkmpptxz fcdbbd73 rem1?? | (empty) (no description set) + Set which revision the branch points to with "jj branch set rem1 -r ". + "###); +} + 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 init_git_remote(test_env: &TestEnvironment, remote: &str) { + let git_repo_path = test_env.env_root().join(remote); + let git_repo = git2::Repository::init(git_repo_path).unwrap(); + let signature = + git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0)).unwrap(); + let mut tree_builder = git_repo.treebuilder(None).unwrap(); + let file_oid = git_repo.blob(remote.as_bytes()).unwrap(); + tree_builder + .insert("file", file_oid, git2::FileMode::Blob.into()) + .unwrap(); + let tree_oid = tree_builder.write().unwrap(); + let tree = git_repo.find_tree(tree_oid).unwrap(); + git_repo + .commit( + Some(&format!("refs/heads/{remote}")), + &signature, + &signature, + "message", + &tree, + &[], + ) + .unwrap(); +} + +fn add_git_remote(test_env: &TestEnvironment, repo_path: &Path, remote: &str) { + init_git_remote(test_env, remote); + test_env.jj_cmd_success( + repo_path, + &["git", "remote", "add", remote, &format!("../{remote}")], + ); +}