Skip to content

Commit

Permalink
merge again if base branch has been updated
Browse files Browse the repository at this point in the history
The current "merge" checkout strategy is unsafe. It merges the PR and the base
branch without holding the directory lock(s), so there is a potentially
very long window where another PR can be applied and be unexpectedly reverted
later.

This happens occasionally if a PR causes plans in multiple directories, but
is almost _guaranteed_ to happen if the initial plan has to wait until a
lock is freed up and a manual "atlantis plan" command is given.

Instead of printing a warning when this happens, we now merge again
if necessary while holding the lock. Plans are then guaranteed to only
be made when merged with the base branch for each directory being planned,
and applying later should be safe even if the base branch sees further
updates.

This fixes/affects runatlantis#804, runatlantis#867, runatlantis#979
  • Loading branch information
finnag committed Mar 21, 2023
1 parent 1674663 commit 2dce6e9
Showing 1 changed file with 14 additions and 16 deletions.
30 changes: 14 additions & 16 deletions server/events/working_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,26 +117,30 @@ func (w *FileWorkspace) Clone(
// We're prefix matching here because BitBucket doesn't give us the full
// commit, only a 12 character prefix.
if strings.HasPrefix(currCommit, p.HeadCommit) {
log.Debug("repo is at correct commit %q so will not re-clone", p.HeadCommit)
return cloneDir, w.warnDiverged(log, p, headRepo, cloneDir), nil
if w.CheckoutMerge && w.recheckDiverged(log, p, headRepo, cloneDir) {
log.Info("base branch has been updated, using merge strategy and will clone again")
} else {
log.Debug("repo is at correct commit %q so will not re-clone", p.HeadCommit)
return cloneDir, false, nil
}
} else {
log.Debug("repo was already cloned but is not at correct commit, wanted %q got %q", p.HeadCommit, currCommit)
}

log.Debug("repo was already cloned but is not at correct commit, wanted %q got %q", p.HeadCommit, currCommit)
// We'll fall through to re-clone.
}

// Otherwise we clone the repo.
return cloneDir, false, w.forceClone(log, cloneDir, headRepo, p)
}

// warnDiverged returns true if we should warn the user that the branch we're
// merging into has diverged from what we currently have checked out.
// recheckDiverged returns true if the branch we're merging into has diverged
// from what we currently have checked out.
// This matters in the case of the merge checkout strategy because after
// cloning the repo and doing the merge, it's possible main was updated.
// Then users won't be getting the merge functionality they expected.
// cloning the repo and doing the merge, it's possible main was updated
// and we have to perform a new merge.
// If there are any errors we return false since we prefer things to succeed
// vs. stopping the plan/apply.
func (w *FileWorkspace) warnDiverged(log logging.SimpleLogging, p models.PullRequest, headRepo models.Repo, cloneDir string) bool {
func (w *FileWorkspace) recheckDiverged(log logging.SimpleLogging, p models.PullRequest, headRepo models.Repo, cloneDir string) bool {
if !w.CheckoutMerge {
// It only makes sense to warn that main has diverged if we're using
// the checkout merge strategy. If we're just checking out the branch,
Expand Down Expand Up @@ -174,13 +178,7 @@ func (w *FileWorkspace) warnDiverged(log logging.SimpleLogging, p models.PullReq
}
}

hasDiverged := w.HasDiverged(log, cloneDir)
if hasDiverged {
log.Info("remote main branch is ahead and thereby has new commits, it is recommended to pull new commits")
} else {
log.Debug("remote main branch has no new commits")
}
return hasDiverged
return w.HasDiverged(log, cloneDir)
}

func (w *FileWorkspace) HasDiverged(log logging.SimpleLogging, cloneDir string) bool {
Expand Down

0 comments on commit 2dce6e9

Please sign in to comment.