$ Git Game

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.

play the bot now

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.