Running rerere in CI integration builds Jump to heading

Some projects assemble an integration branch by merging many topic branches together: Git’s own seen branch is built this way, and so are release candidates in teams that collect features before shipping. Doing it by hand each night is tedious, and most of the conflicts are the same ones as the night before. A CI job can do the assembly, using a stored rerere cache to resolve every conflict a person has already resolved once. The job only needs a human when a genuinely new conflict appears, and it can say exactly which topic pair caused it. This page builds that job, within rerere conflict automation.

When to use this approach Jump to heading

  • You build an integration or release-candidate branch by merging a set of topic branches.
  • Topic branches are long-lived enough that their conflicts with each other recur nightly.
  • People already resolve those conflicts and would rather resolve each one once.
  • You understand rerere’s model; if not, start with automating repeated conflict resolution with rerere.

Step 1 β€” Store the rr-cache where CI can read and write it Jump to heading

The cache must persist between runs and be editable by people who resolve new conflicts. A dedicated branch holding the cache directory is simple and reviewable.

# One-time setup: an orphan branch holding the rerere cache
git switch --orphan rr-cache-store
git rm -rf . >/dev/null 2>&1 || true
mkdir rr-cache && touch rr-cache/.keep
git add rr-cache && git commit -m "Initialise shared rerere cache"
git push origin rr-cache-store
git switch main
The nightly integration jobThe job restores the shared rerere cache, starts from main, merges each listed topic branch in order with rerere enabled, and either pushes the assembled integration branch or stops at the first unresolved conflict and reports which topic caused it.Restore cacherr-cache-store branchStartfrom mainMerge topicsin listed orderAll resolved?rerere + autoupdatePush / reportbranch or conflictthe order of topics is part of the input β€” keep it in a file

Step 2 β€” List the topics to integrate Jump to heading

The set and order of topic branches is configuration. Keep it in a file on main so changes to the integration plan are reviewed.

# integration/topics.txt β€” merged in this order
topic/new-billing-api
topic/search-ranking
topic/export-scheduling
topic/ui-redesign

Step 3 β€” Assemble the branch with rerere enabled Jump to heading

The job restores the cache into .git/rr-cache, merges each topic, and uses rerere.autoUpdate so reused resolutions are staged without a human. Any file still conflicted after rerere runs means a new conflict.

#!/bin/sh
# ci/assemble-integration.sh
set -eu
git config rerere.enabled true
git config rerere.autoUpdate true
git config user.name integration-bot; git config user.email [email protected]

git fetch origin rr-cache-store
git archive origin/rr-cache-store rr-cache | tar -x -C "$(git rev-parse --git-dir)"

git switch -C integration origin/main
while read -r topic; do
  case "$topic" in ''|\#*) continue ;; esac
  if ! git merge --no-ff --no-edit "origin/$topic"; then
    left=$(git diff --name-only --diff-filter=U)
    if [ -n "$left" ]; then
      echo "NEW CONFLICT merging $topic:"; echo "$left"
      exit 1
    fi
    git commit --no-edit            # rerere resolved everything
  fi
done < integration/topics.txt
git push --force origin integration

The integration branch is rebuilt from scratch every night, so force-pushing it is expected; nobody should base work on it.

⚠️ SAFETY WARNING: With rerere.autoUpdate, reused resolutions are committed without anyone reviewing them. That is acceptable for a throwaway integration branch that only feeds testing, and not acceptable for a branch that is deployed or released. If the integration branch ships, drop autoupdate and require a person to confirm each reused resolution.

Step 4 β€” Turn a new conflict into a recorded resolution Jump to heading

When the job reports a new conflict, a person reproduces the merge locally with the same cache, resolves it once, and pushes the updated cache. The next nightly run reuses it.

git fetch origin rr-cache-store integration
git archive origin/rr-cache-store rr-cache | tar -x -C "$(git rev-parse --git-dir)"
git config rerere.enabled true
git switch -C integration-fix origin/main
# replay the topics up to the one that failed (the job log names it), then:
git merge origin/topic/export-scheduling
# resolve, add, commit β€” rerere records the resolution
git switch rr-cache-store
cp -r "$(git rev-parse --git-dir)/rr-cache/." rr-cache/
git add rr-cache && git commit -m "Record resolution: export-scheduling vs search-ranking"
git push origin rr-cache-store
A new conflict, resolved onceThe nightly job hits a conflict rerere does not know and reports it. A person reproduces the merge with the shared cache, resolves it, and pushes the updated cache. The following night the job reuses that resolution and completes.nightly jobpersonrr-cache-storenew conflict: export vs searchrestore cacheresolve oncepush updated cachenext night: auto-resolvedeach conflict between a pair of topics costs one human resolution, ever Human resolutions needed per nightAn illustrative month of nightly integration builds. The first week needs several human resolutions as the cache fills. After that, nights with no new conflicts need none, and occasional spikes coincide with a topic branch being heavily reworked.human resolutions per week (illustrative)week 114week 25week 31week 43the cache converges quickly; spikes mean a topic changed shape, not that rerere failed

Step 5 β€” Keep the cache from growing stale Jump to heading

Topic branches change, and old resolutions stop matching anything. Prune the shared cache periodically by running a fresh assembly with an empty cache copy and keeping only the entries it used.

# Entries touched by the last successful run are the ones worth keeping
find "$(git rev-parse --git-dir)/rr-cache" -mindepth 1 -maxdepth 1 -type d -mtime +90 -print

rerere updates timestamps on entries it reuses, so entries not touched in ninety days are almost certainly obsolete. Review the list and remove them from the cache branch.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Why store the cache on a branch instead of a CI cache? Jump to heading

CI caches are opaque and easy to lose, and people cannot push resolutions into them. A branch is versioned, reviewable, and shared between CI and humans with ordinary Git commands.

What if two topics conflict differently depending on merge order? Jump to heading

They can: merging A then B produces different conflict hunks from B then A. Keep the order fixed in the topics file, and change it only deliberately, expecting to resolve some conflicts again.

Should the integration branch run the full test suite? Jump to heading

Yes β€” that is the reason to build it. Conflicts are the visible problem; the integration build’s real value is catching topics that merge cleanly but break each other.