Git merge conflict cheatsheetEdit
git status
shows files with conflicts under "Unmerged paths".
Merge conflict markers
Summarizing "Understanding Git conflict markers", the "top" half of a conflict is the branch you’re merging into, and the "bottom" half of a conflict is the branch you are trying to merge, so:
Normal merges
- Top shows local changes (AKA "ours")
- Bottom shows upstream changes (AKA "theirs")
Rebases
- Top shows upstream changes (AKA "ours")
- Bottom shows "local" (to topic branch) changes (AKA "theirs")
Stashes
- Top shows worktree state before running
git stash pop
- Bottom shows worktree state that you originally stashed with
git stash
- Top shows upstream changes (AKA "ours")
- Bottom shows "local" (to topic branch) changes (AKA "theirs")
Stashes
- Top shows worktree state before running
git stash pop
- Bottom shows worktree state that you originally stashed with
git stash
git stash pop
git stash
Conflict markers look like this:
<<<<<<< Updated upstream
foo
=======
bar
>>>>>>> Stashed changes
Index contents
- stage 1: common ancestor; see with
git show :1:file
- stage 2: (local) HEAD; see with
git show :2:file
- stage 3: MERGE_HEAD; see with
git show :3:file
git show :1:file
git show :2:file
git show :3:file
git ls-files -u
shows the index metadata (paths, blob IDs) for all stages of unmerged paths.
Scenario: picking one side of the merge
This can be useful when dealing with conflicts due to things like formatting changes. Say you have a topic branch and run a source formatter on it. You rebase that topic branch onto master
and get conflicts because somebody changed the code that you formatted. The easiest solution here is not to solve the merge conflict by hand, but rather take the master
changes and then re-run the source formatting operation.
git checkout --ours -- path/to/conflicting-file
sh format-source.sh
Again, note that during rebasing the "ours"/"theirs" distinction is reversed:
- During rebase: "ours" is the upstream branch, "theirs" is the topic branch.
- During a normal merge: "ours" is the current branch, "theirs" is the other branch being merged in.
Scenario: git stash pop
As with rebase conflicts, the yours-vs-theirs distinction can be a little confusing.
HEAD
(stage 2 in the index) refers to the worktree state you had before runninggit stash pop
MERGE_HEAD
(stage 3 in the index) refers to the worktree state you recorded withgit stash
So, say you want to just keep things as they were for a particular file and ignore what’s in the stash, you could do:
$ git show :2:filename > filename
$ git add filename
$ git stash drop
But note: in a merge conflict situation hunks that applied cleanly are added to the index, so once you’ve resolved a conflict like this you may have stuff staged that you aren’t necessarily ready to commit. In that case, git reset HEAD
.
Scenario: ignoring whitespace changes
This can be helpful to avoid conflicts arising from indentation changes (say, the main
branch has a change that ends up indenting a method body):
$ git cherry-pick -Xignore-all-space 15e087dfc205
Aliases
[alias]
# Mark merge conflicts as resolved.
resolve = "!f() { \
git add $(git diff --name-only --diff-filter=U -- \"${@:-.}\"); \
}; f"
git continue
subcommand
[alias]
# Mark merge conflicts as resolved.
resolve = "!f() { \
git add $(git diff --name-only --diff-filter=U -- \"${@:-.}\"); \
}; f"
git continue
subcommand
Save under $PATH
as git-continue
:
#!/usr/bin/env zsh
# Runs `git rebase --continue`, `git cherry-pick --continue` as appropriate,
# based on context.
#
# Based on: https://stackoverflow.com/a/53370600
GIT_DIR=$(git rev-parse --git-dir)
STATUS=$?
if [ $STATUS -ne 0 ]; then
exit $STATUS
fi
if [ -d "${GIT_DIR}/rebase-merge" ]; then
exec git rebase --continue
elif [ -d "${GIT_DIR}/rebase-apply" ]; then
exec git rebase --continue
elif [ -f "${GIT_DIR}/MERGE_HEAD" ]; then
exec git merge --continue
elif [ -f "${GIT_DIR}/CHERRY_PICK_HEAD" ]; then
exec git cherry-pick --continue
elif [ -f "${GIT_DIR}/REVERT_HEAD" ]; then
exec git revert --continue
else
echo "error: No operation to --continue"
exit 1
fi
# vim: set filetype=zsh