Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Force-checkout remote branch to fix unrelated histories #228

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

ofalvai
Copy link
Contributor

@ofalvai ofalvai commented Dec 11, 2024

Checklist

  • I've read and followed the Contribution Guidelines
  • step.yml and README.md is updated with the changes (if needed)

Version

Requires a MAJOR/MINOR/PATCH version update

Context

Git clones can fail in persistent environments (bare-metal runners) when the build is triggered by a push event. Imagine this sequence:

  1. Build 1 runs with the default step settings. It fetches origin/main (because it's not a PR build) with --depth=1 (default step setting).
  2. New commits are pushed to origin/main
  3. Build 2 runs: it fetches origin/main again with --depth=1, but when it tries to checkout and merge the local branch, it fails with the refusing to merge unrelated histories error.

An actual step log:

$ git "init"
Reinitialized existing Git repository in /home/bitrise-admin/ca71a338453a42a3/git/.git/
$ git "config" "gc.auto" "0"
$ git "fetch" "--jobs=10" "--depth=1" "--no-tags" "origin" "refs/heads/master"
From github.com:bitrise-io/ubuntu-android
 * branch            master     -> FETCH_HEAD
 + d0413b1...db22ecf master     -> origin/master  (forced update)
$ git "checkout" "master"
Switched to branch 'master'
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
$ git "merge" "origin/master"
fatal: refusing to merge unrelated histories
Checkout strategy used: gitclone.checkoutBranch
Failed to execute Step:
  updating branch (master) failed:
    fatal: refusing to merge unrelated histories

Changes

Branch checkout was implemented using a fetch + checkout + merge remote local sequence. This doesn't work in persistent envs because there is a leftover local branch from the previous run with a single commit (because of shallow fetching). The new remote-tracking branch also has a single commit, and there is no common ancestor of these two commits, so the merge command fails.

This PR changes the implementation of our branch checkout so that any previous local branch state is overwritten, sort of like a git pull --force.

The end result remains the same:

  • The branch is checked out
  • It is not behind origin
  • If the local branch already exists, it's overwritten
  • [new] If the local and remote branches diverged, the remote state wins

Investigation details

Decisions

@ofalvai ofalvai changed the title Force-checkout remote branch to resolve unrelated histories Force-checkout remote branch to fix unrelated histories Dec 11, 2024
@@ -99,31 +99,21 @@ func checkoutWithCustomRetry(gitCmd git.Git, arg string, retry fallbackRetry) er
return nil
}

func fetchInitialBranch(gitCmd git.Git, remote string, branchRef string, fetchTraits fetchOptions) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming is hard 😵‍💫

return fmt.Errorf("%v: %w", wErr, errors.New("please make sure the branch still exists"))
}

if err := checkoutWithCustomRetry(gitCmd, branch, nil); err != nil {
return handleCheckoutError(
Copy link
Contributor Author

@ofalvai ofalvai Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom retry here adds nicer error message (a list of existing branch names), but if a branch doesn't exist, I think it fails in the fetch step above.
I don't have a strong opinion on this, but I don't like this abstraction and I can easily say goodbye to this.
I was also thinking about adding -B to this checkout wrapper, but it only works in the context of checking out branches, while the wrapper is used for tags and commits as well.

@ofalvai ofalvai marked this pull request as ready for review December 11, 2024 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant