The first four commands
In order, before opening an editor. Each one answers a question the next one depends on.
git status --short— every unmerged path, with its kind in two letters. The file that needs the most thought is often the one that looks untouched.git log --oneline -1 HEADandgit log --oneline -1 MERGE_HEAD— which commit is on each side, sooursandtheirsstop being words you reason about. During a rebase the second one isREBASE_HEAD, and the sides are inverted.git show :1:<path>— the base. What both sides were changing from, which is what tells you whether two changes are incompatible or merely adjacent.git ls-files -u <path>— the stage table, when the kind is not obvious. A missing stage is the answer, and no porcelain command shows an absence.
The three kinds
Not “markers appeared in a file”. A conflict is a path git refused to resolve. Two of the three kinds below put no markers anywhere — the file sits in your working tree looking perfectly normal, and it is unmerged.
| Kind, as git names it | What it is, and what it needs from you |
|---|---|
CONFLICT (content) | Both sides changed the same region relative to the base. Markers in the file. Usually resolved with lines from both sides — which is neither --ours nor --theirs. |
CONFLICT (modify/delete) | One side changed the file, the other removed it. No markers. This is a question about intent, not about lines: is the file still needed? |
CONFLICT (add/add) | Both sides created the same path independently. Markers, but no base section — there is nothing to three-way merge against. A decision about which implementation survives. |
The dangerous one is modify/delete. Git refuses the commit while the path is unmerged, so it is caught — but the obvious fix, git add <path>, silently keeps your version and discards the other side’s deletion. Nothing warns you.
Reading `git status --short`
Normally the two columns mean index and working tree. For an unmerged path they do not: git-status keeps a second table in which the first letter is ours and the second is theirs (git-status). Applying the ordinary reading to UD gets you the wrong side.
| Code | Meaning while unmerged |
|---|---|
UU | Both modified — the ordinary content conflict. |
AA | Both added — the add/add case, no base. |
UD | We modified, they deleted. Stages 1 and 2, no stage 3. |
DU | We deleted, they modified. Stages 1 and 3, no stage 2. |
AU | Added by us — they have no version at all. |
UA | Added by them — we have no version at all. |
DD | Both deleted. Rare, and resolved by agreeing it is gone. |
What the index is holding
Outside a conflict the index holds one entry per path. During one it holds up to three: “stage 1 stores the version from the common ancestor, stage 2 from HEAD, and stage 3 from MERGE_HEAD” (git-merge). The stage column is a general property of the index rather than a merge invention (git-read-tree).
100644 0ef3a021… 1 config.ymlstage 1 — the merge base100644 209b4a6e… 2 config.ymlstage 2 — ours, i.e. HEAD100644 a1bc9402… 3 config.ymlstage 3 — theirs100644 f2dc4e9c… 1 legacy.txtbase exists…100644 3ea82779… 2 legacy.txt…ours exists, theirs does not → modify/delete100644 5d6e9041… 2 retry.tsno stage 1 at all…100644 179e71aa… 3 retry.ts…both sides created it → add/addgit ls-files -u on a merge with all three kinds in it. Count the stages per path: the missing one names the kind. A missing stage is not a gap in the table, it is a fact about the graph — retry.ts has no stage 1 because the file does not exist at the merge base.| Read a stage back | Gives you |
|---|---|
git show :1:<path> | The base — what both sides started from. The most useful and least used of the three. |
git show :2:<path> | ours: HEAD’s version. During a rebase this is the branch you are replaying onto, not your work. |
git show :3:<path> | theirs: MERGE_HEAD’s version, or the commit being replayed during a rebase. |
git diff --base <path> | Both sides against the base at once, when reading three files separately gets confusing. |
The markers, with the base shown
The default marker style hides the base, which is the piece that decides most resolutions. zdiff3 prints it: the alternate styles add “a ||||||| marker and the original text before the ======= marker” (git-config). Turn it on once — git config --global merge.conflictStyle zdiff3 — and every conflict you meet afterwards tells you more.
<<<<<<< oursHEAD — the branch you are ontimeout: 30000we changed this lineretries: 3we left this one alone||||||| basethe merge base — only with diff3/zdiff3timeout: 5000what both sides started fromretries: 3=======timeout: 5000they left this one aloneretries: 5they changed this line>>>>>>> theirsMERGE_HEAD, or the replayed commitTo bring the markers back after you have mangled a file: git checkout --conflict=zdiff3 <path> restores the conflicted state from the index, which is still holding all three stages.
git checkout --ours <path> and --theirs <path> take an entire file from one stage. Right for genuinely binary decisions — a lockfile, a generated bundle, a minified asset — and wrong for source, where the answer usually needs lines from both.
Resolving the same conflict twice
rerere — reuse recorded resolution — replays a resolution you have already given for a conflict of the identical shape. It is off by default: git config --global rerere.enabled true.
| Command | What it does |
|---|---|
git rerere status | Paths whose resolution it will record. Content conflicts only — a deletion has no text resolution to remember. |
git rerere diff | What your resolution changed, against the recorded preimage. |
git rerere forget <path> | Throws away a resolution you recorded wrongly. Documented in the man page and nowhere in the book. |
git rerere remaining | Paths still unresolved after the replay. |
Two properties matter more than the commands, and both are easy to get backwards.
It does not stage anything. The docs are explicit: rerere “leaves the index file alone, so you still need to do the final sanity checks with git diff (or git diff -c) and git add when you are satisfied” (git-rerere). git status still says UU after a replay. That is the safety property — a remembered resolution is a draft, not a decision.
It learns on git commit, not on git add. The preimage is written when the conflict happens; the postimage — your actual answer — appears only when you commit, which is also when the Recorded resolution lines print. So a conflict you resolve, stage, and then git merge --abort teaches it nothing. Established by watching .git/rr-cache/<id>/ before and after; the man page hints at it and leaves the consequence to the reader.
And it is yours alone. .git/rr-cache is a local directory: it is not pushed, and a fresh clone does not have it. A teammate who hits the same conflict resolves it by hand.
Getting out
| Command | Effect |
|---|---|
git merge --abort | Back to before the merge, cleanly, at any point. Nothing is lost. |
git rebase --abort | The same for a rebase, however many commits in. |
git rebase --skip | Drops the commit being replayed and continues. Occasionally right, and worth being sure about. |
git reset --merge | Abandons the merge but keeps unrelated local changes that were there before it started. |
Knowing these exist is what makes it safe to explore a conflict rather than guess at it.
- Pro Git §7.8 — Advanced MergingWorks the stage table by hand, plus
git merge-file,checkout --conflict,git log --mergeand the combined diff format. Does not name the conflict kinds. - git-merge — the stage numbersThe mapping of stage 1/2/3 to base,
HEADandMERGE_HEAD. - git-status — the unmerged tableThe second short-format table, in which the two columns mean ours and theirs rather than index and working tree.
- git-rerereThe “leaves the index file alone” guarantee, and
forget, which the book does not cover.