ok/jj
1
0
Fork 0
forked from mirrors/jj

Don't detatch Git HEAD when advance-branches is enabled for a branch

When setting the working copy commit, if a single branch points to a parent of
the working copy and advance-branches is enabled for that branch, set Git HEAD
to the branch instead of detatching at the parent commit.
This commit is contained in:
Evan Mesterhazy 2024-02-23 14:52:31 -05:00
parent 8a218d2d7c
commit 2cd99190b1
3 changed files with 52 additions and 9 deletions

View file

@ -1266,11 +1266,30 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
.transpose()?;
if self.working_copy_shared_with_git {
let git_repo = self.git_backend().unwrap().open_git_repo()?;
if let Some(wc_commit) = &maybe_new_wc_commit {
git::reset_head(tx.mut_repo(), &git_repo, wc_commit)?;
}
// TODO(emesterhazy): Is it problematic that we're exporting these
// refs before resetting head? If the ref export fails, the head
// won't be reset. We could defer returning the error until after
// HEAD is reset, but we need to try to export the refs first so
// that we can set HEAD to an advanceable branch if one exists.
let failed_branches = git::export_refs(tx.mut_repo())?;
print_failed_git_export(ui, &failed_branches)?;
if let Some(wc_commit) = &maybe_new_wc_commit {
// If there's a single branch pointing to one of the working
// copy's parents and advance-branches is enabled for it, then
// set the Git HEAD to that branch instead of detaching at the
// commit. Ignore errors since it's too late to bail out without
// losing any work.
let parent_branch = match self.get_advanceable_branches(wc_commit.parent_ids()) {
Ok(branches) if branches.len() == 1 => Some(branches[0].name.clone()),
_ => None,
};
git::reset_head(
tx.mut_repo(),
&git_repo,
wc_commit,
parent_branch.as_deref(),
)?;
}
}
self.user_repo = ReadonlyUserRepo::new(tx.commit(description));
self.report_repo_changes(ui, &old_repo)?;

View file

@ -904,20 +904,43 @@ fn update_git_ref(
}
/// Sets `HEAD@git` to the parent of the given working-copy commit and resets
/// the Git index.
/// the Git index. If `try_branch` points to the parent of the given
/// working-copy commit, then the Git HEAD is set to the branch instead of being
/// detached.
pub fn reset_head(
mut_repo: &mut MutableRepo,
git_repo: &git2::Repository,
wc_commit: &Commit,
try_branch: Option<&str>,
) -> Result<(), git2::Error> {
let first_parent_id = &wc_commit.parent_ids()[0];
// Try to look up the branch reference if `try_branch` is provided, but
// don't return an error and proceed to detach HEAD it the lookup fails.
// Setting HEAD to a branch instead of a commit provides a better Git
// interop experience for CLI users that enable the "advance-branches"
// feature.
let branch_ref = if let Some(branch) = try_branch {
git_repo.resolve_reference_from_short_name(branch).ok()
} else {
None
};
if first_parent_id != mut_repo.store().root_commit_id() {
let first_parent = RefTarget::normal(first_parent_id.clone());
let git_head = mut_repo.view().git_head();
let new_git_commit_id = Oid::from_bytes(first_parent_id.as_bytes()).unwrap();
let new_git_commit = git_repo.find_commit(new_git_commit_id)?;
if git_head != &first_parent {
git_repo.set_head_detached(new_git_commit_id)?;
if let Some(branch_ref) = branch_ref {
let branch_commit = branch_ref.peel_to_commit()?.id();
if branch_commit == new_git_commit_id {
// Set Git HEAD to the branch pointing to the parent Git
// commit instead of detaching.
git_repo.set_head_bytes(branch_ref.name_bytes())?;
}
} else {
git_repo.set_head_detached(new_git_commit_id)?;
}
mut_repo.set_git_head_target(first_parent);
}
git_repo.reset(new_git_commit.as_object(), git2::ResetType::Mixed, None)?;
@ -941,6 +964,7 @@ pub fn reset_head(
git_repo.cleanup_state()?;
mut_repo.set_git_head_target(RefTarget::absent());
}
Ok(())
}

View file

@ -1942,7 +1942,7 @@ fn test_reset_head_to_root() {
.unwrap();
// Set Git HEAD to commit2's parent (i.e. commit1)
git::reset_head(tx.mut_repo(), &git_repo, &commit2).unwrap();
git::reset_head(tx.mut_repo(), &git_repo, &commit2, None).unwrap();
assert!(git_repo.head().is_ok());
assert_eq!(
tx.mut_repo().git_head(),
@ -1950,7 +1950,7 @@ fn test_reset_head_to_root() {
);
// Set Git HEAD back to root
git::reset_head(tx.mut_repo(), &git_repo, &commit1).unwrap();
git::reset_head(tx.mut_repo(), &git_repo, &commit1, None).unwrap();
assert!(git_repo.head().is_err());
assert!(tx.mut_repo().git_head().is_absent());
@ -1958,7 +1958,7 @@ fn test_reset_head_to_root() {
git_repo
.reference("refs/jj/root", git_id(&commit1), false, "")
.unwrap();
git::reset_head(tx.mut_repo(), &git_repo, &commit2).unwrap();
git::reset_head(tx.mut_repo(), &git_repo, &commit2, None).unwrap();
assert!(git_repo.head().is_ok());
assert_eq!(
tx.mut_repo().git_head(),
@ -1967,7 +1967,7 @@ fn test_reset_head_to_root() {
assert!(git_repo.find_reference("refs/jj/root").is_ok());
// Set Git HEAD back to root
git::reset_head(tx.mut_repo(), &git_repo, &commit1).unwrap();
git::reset_head(tx.mut_repo(), &git_repo, &commit1, None).unwrap();
assert!(git_repo.head().is_err());
assert!(tx.mut_repo().git_head().is_absent());
// The placeholder ref should be deleted