CONFLICT (content): a merge conflict
Two commits changed the same lines of the same file, and Git won't guess which is right. The pull stops, leaves both versions in the file, and waits for you.
$ git pull --no-rebase From ../origin ab5768d..7a20dd4 main -> origin/main Auto-merging api.py CONFLICT (content): Merge conflict in api.py Automatic merge failed; fix conflicts and then commit the result. $ git diff diff --cc api.py index 4aa2239,48fcc42..0000000 --- a/api.py +++ b/api.py @@@ -1,2 -1,2 +1,6 @@@ line 2 ++<<<<<<< HEAD +line 4 ++======= + line 3 ++>>>>>>> 7a20dd4815012ed0c8bcd4fad2eae46182e82c61 $ git add api.py $ git commit --no-edit [main e1078fd] Merge branch 'main' of ../origin
What Git checked
A pull first fetches (main -> origin/main: the remote's main moved), then joins
the new commit with yours. Auto-merging api.py says Git tried: it took the version both commits
started from, bot's version and yours, and merged them line by line. Where only one side changed a line, that
side wins silently. Where both changed the same line there is no rule, so Git reports
CONFLICT (content) and the file, and the merge stops:
Automatic merge failed; fix conflicts and then commit the result.
The file now holds both sides between markers, and git diff shows them: between
<<<<<<< HEAD and ======= your side, between
======= and >>>>>>> theirs, the commit being merged in. Lines
Git could settle on its own sit outside the markers.
What to do now
Edit the file until it says what it should, markers gone. git add it, which tells Git the
conflict is resolved, and git commit to finish the merge; with no message of your own, Git's
(Merge branch 'main' of …) does. The merge commit has two parents, bot's commit and yours, and
your main now holds both. To get out instead, git merge --abort puts everything back
as it was before the pull.
The same conflict under a rebase
$ git pull --rebase From ../origin ab5768d..7a20dd4 main -> origin/main Auto-merging api.py CONFLICT (content): Merge conflict in api.py error: could not apply a97e6d9... api: rename the endpoint hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". hint: Disable this message with "git config set advice.mergeConflict false" Could not apply a97e6d9... # api: rename the endpoint $ git status interactive rebase in progress; onto 7a20dd4 Last command done (1 command done): pick a97e6d9 # api: rename the endpoint No commands remaining. You are currently rebasing branch 'main' on '7a20dd4'. (fix conflicts and then run "git rebase --continue") (use "git rebase --skip" to skip this patch) (use "git rebase --abort" to check out the original branch) Unmerged paths: (use "git restore --staged <file>..." to unstage) (use "git add <file>..." to mark resolution) both modified: api.py no changes added to commit (use "git add" and/or "git commit -a") $ git add api.py $ git rebase --continue [detached HEAD f842f6d] api: rename the endpoint 1 file changed, 1 insertion(+) Successfully rebased and updated refs/heads/main.
pull --rebase doesn't merge; it takes your commit off and replays it on top of bot's. The replay
hits the same clash, prints the same CONFLICT line, and adds
error: could not apply …: the commit it was replaying. git status says where you
are: rebasing main onto bot's commit, with api.py both modified. Resolve the file,
git add it, and git rebase --continue; Git finishes the replay and prints
Successfully rebased and updated refs/heads/main. There is no merge commit, only your commit,
rewritten on top. git rebase --abort is the way back.
Letting one side win: -X ours, -X theirs
When one side should simply win, a strategy option settles every clash without stopping:
-X ours keeps one side, -X theirs the other, and only for the lines that clashed.
Under a merge, ours is your branch. Under a rebase the names swap: the branch you're replaying onto, bot's
main, is ours, and the commit being replayed, yours, is theirs. So during
pull --rebase, -X ours keeps bot's line and -X theirs keeps yours:
$ git pull --rebase -X ours From ../origin ab5768d..7a20dd4 main -> origin/main dropping a97e6d99dfdd52af42194ffe51de7d063c9a4a53 api: rename the endpoint -- patch contents already upstream Successfully rebased and updated refs/heads/main. $ git log --oneline 7a20dd4 api: add the endpoint ab5768d api: the first endpoint 07dec17 readme: the project
With -X ours, bot's version wins the clash and nothing of your commit is left to apply, so Git
drops it (patch contents already upstream) and your main is bot's.
$ git pull --rebase -X theirs From ../origin ab5768d..7a20dd4 main -> origin/main Successfully rebased and updated refs/heads/main. $ git log --oneline c2b4aa8 api: rename the endpoint 7a20dd4 api: add the endpoint ab5768d api: the first endpoint 07dec17 readme: the project $ git diff origin/main diff --git a/api.py b/api.py index 48fcc42..4aa2239 100644 --- a/api.py +++ b/api.py @@ -1,2 +1,2 @@ line 2 -line 3 +line 4
With -X theirs, your commit lands on top with your line, and git diff origin/main
shows the one line that differs: bot's replaced by yours. Bot's commit is still in the history; its change to
that line is not in the file.
Why the game does this to you
In Git Game a commit is a card, and a conflict is a pull that brings in a commit touching the same file as one
of your unpushed commits. Nobody is there to edit a file, so the pull's strategy settles it, declared on the
op: keep theirs, and yours is dropped; keep mine, and theirs is overwritten, its author taking a grudge
against you; or keep both, which is resolving by hand and costs one more op. The hub shows the choice by what
it does and prints the flag Git would use, which swaps under --rebase, as above. The one place
the game is coarser than Git: a card is whole, so the losing side loses its commit, where Git loses only the
lines that clashed.
Play a day where this happens
One tap starts a game against the bot. Five files in the deck, two players: the same file comes up soon.
Output captured from Git 2.50.1 (Apple Git-155) by scripts/git-output.sh, in a scratch repository with two clones, ana's and bot's, and fixed dates, so the hashes are the same on every run. It is captured again at every Git release.