Git Operations in Atlantis#
Overview#
All git operations in Atlantis are handled by FileWorkspace, which implements the WorkingDir interface in server/events/working_dir.go. FileWorkspace manages cloning, updating, divergence detection, and cleanup of per-PR working directories. The two checkout strategies it supports β branch and merge β drive all downstream git logic.
Checkout Strategies#
FileWorkspace.CheckoutMerge selects the strategy:
- Branch strategy (
CheckoutMerge=false): shallow-clones the PR head branch at depth 1. No divergence detection runs. - Merge strategy (
CheckoutMerge=true): clones the base branch, then merges the PR head into it so Atlantis always plans against the post-merge state. All divergence and prune logic applies to this mode.
Key Entry Points#
| Method | Purpose |
|---|---|
Clone | Initial checkout; uses fast path (read lock + git rev-parse) to skip re-cloning if already at the right commit |
MergeAgain | Re-merges when the base branch has advanced; uses leader/follower pattern to avoid write-lock contention |
HasDiverged | Checks if base branch has new commits; only active when CheckoutMerge=true |
GetDivergedFiles | Returns files changed by divergent base commits (for when_modified targeting) |
updateToRef | Updates an existing clone to a new commit; redoes the merge under merge strategy |
mergeToBaseBranch | Core merge routine: fetch PR head β check merge base β git merge --no-ff |
Merge Checkout Strategy Flow#
forceClone implements the initial merge checkout:
- Removes and recreates the clone directory.
- Clones the base branch (with optional
CheckoutDepthfor shallow clones) . - Adds the PR head as the
sourceremote whenusesPRSourceRemoteis true (non-GitHub-App path) . - Calls
mergeToBaseBranch, which:- Fetches the PR head:
pull/<N>/headfromoriginfor GitHub App;+refs/heads/<branch>:fromsourceremote otherwise . - Checks for a merge base; if not found (shallow clone too shallow), falls back to
git fetch --unshallow. - Runs
git merge -q --no-ff -m "atlantis-merge" FETCH_HEAD.--no-ffforces a merge commit sogit rev-parse HEAD^2reliably returns the PR head commit .
- Fetches the PR head:
For existing clones, updateToRef re-does the merge:
git reset --hard origin/<base-branch>- Calls
mergeToBaseBranchagain. - Verifies
HEAD^2matches the expectedtargetRef. - Calls
cleanStalePlanFilesto remove untracked.tfplanfiles viagit clean -f -x -e .terragrunt-cache -- ':(glob)**/*.tfplan'(sincegit reset --hardleaves untracked files behind).
When the base branch advances after an initial clone, MergeAgain triggers a re-merge via mergeAgain: reset to refs/remotes/origin/<base-branch>, then call mergeToBaseBranch.
Branch Retargeting#
If remoteHasBranch finds that the base branch no longer exists in the remote (e.g., the PR was retargeted to a different base), attemptReuseCloneDir returns false and forces a full reclone .
Divergence Detection#
Only active when CheckoutMerge=true. Key internal functions:
hasDiverged: runsgit fetchthengit status --untracked-files=no; checks for"have diverged"in output. Returnstrueon any error (fail-safe).getDivergedFilesFromRef: runsgit fetchthengit log <startRef>..<origin/base-branch> --name-only --format=to find files changed by commits the workspace hasn't seen.hasDivergedForPatterns: used whenautoplanWhenModifiedpatterns are set. Gets the diverged file list, filters it throughpatternmatcher, and reports divergence only if at least one file matches.recheckDiverged: called byMergeAgain. Refreshes remote URLs (to handle expired GitHub App credentials), runsgit remote update [--prune]orgit remote update origin [--prune], then delegates tohasDiverged. Returnstrueon any error.
Error Propagation (PR #6632)#
PR #6632 fixed a bug where fetch errors in divergence checks were silently swallowed and treated as "assume divergence," allowing plans to proceed against a potentially stale workspace. After the fix, fetch failures abort the plan/apply operation via the command requirement handler.
Stale Remote-Tracking Ref Pruning (PR #6631)#
Problem: Without pruning, if a remote branch is deleted or renamed, its stale remote-tracking ref (e.g., refs/remotes/origin/feature/foo) remains as a directory entry under .git/refs/remotes/. If a new branch is later created whose name occupies the same path prefix (e.g., origin/feature), git fails with cannot lock ref / exists; cannot create. Previously, recheckDiverged treated this fetch failure as "assume divergence" and swallowed the error.
Fix (PR #6631): --prune was added to all git fetch/update operations in working_dir.go:
| Location | Before | After |
|---|---|---|
recheckDiverged (non-App path) | git remote update | git remote update --prune |
recheckDiverged (origin-only) | git remote update origin | git remote update origin --prune |
hasDiverged | git fetch | git fetch --prune |
getDivergedFilesFromRef | git fetch | git fetch --prune |
updateToRef (all remotes) | fetch --all | fetch --all --prune |
updateToRef (origin-only) | fetch origin | fetch origin --prune |
mergeToBaseBranch (unshallow) | fetch --unshallow | fetch --unshallow --prune |
A regression test TestMergeAgain_PrunesConflictingRefNames in working_dir_test.go reproduces and verifies the fix.
Locking Model#
FileWorkspace uses a sync.Map called gitLocks with two key namespaces :
repo-lock/<cloneDir>β*sync.RWMutex. Write-locked byClone,MergeAgain, andupdateToRef; read-locked byHasDiverged,HasDivergedFromPullHead,GetDivergedFiles,recheckDiverged, and plan/apply step execution viaGitReadLock.ref-lock/<cloneDir>β*sync.Mutex. Serializes fetch and remote-update operations independently to reduce contention.
The read lock is always acquired before the ref lock to avoid deadlock .
Concurrent Merge Coordination#
MergeAgain uses a pendingMerge value in a pendingMerges sync.Map to avoid multiple goroutines serializing on the write lock. The first goroutine (leader) acquires the write lock and performs the merge; others wait on a done channel and reuse the leader's result . The pendingMerge entry is closed and deleted while the write lock is still held, preventing new callers from joining a stale completed merge .