Git edge cases

The usual software engineer knows about the basic commands of git. Commiting changes, fetching changes, creating branches, pulling different versions, adding remotes. But there are some edge cases where this basic knowledge is not sufficient. This article tries to cover these edge cases and provide solutions and best practices for them.

Merge

A merge creates a new commit that incorporates changes from other commits. Before merging, the stage must match the current commit. The trivial case is if the other commit is an ancestor of the current commit, in which case nothing is done. The next most simple is if the current commit is an ancestor of the other commit. This results in a fast-forward merge. The reference is simply moved, and then the new commit is checked out. Otherwise, a “real” merge must occur. You can choose other strategies, but the default is to perform a “recursive” merge, which basically takes the current commit, the other commit, and their common ancestor, and performs a three-way merge. The result is saved to the working directory and the stage, and then a commit occurs, with an extra parent for the new commit.

Conflicts

From the series of edge cases the most common one are the conflicts. Imagine that situation where you made a change and one of your team mates made some changes on the same file what you have modified. In such case a conflict occur, and git is going to ask you to resolve it. On windows for this case you better have a merge/diff tool installed and configured to be used for git.

Registering a diff tool with Git

I recommend WinMerge or Meld. You can register the diff/merge tool with git using the following way:

1. Create a wrapper script “git-diff-wrapper.sh” which contains something like

#!/bin/sh

# diff is called by git with 7 parameters:
# path old-file old-hex old-mode new-file new-hex new-mode

"<path_to_diff_executable>" "$2" "$5" | cat



and put this script file in your home folder (e.g. C:\Users\<your_username>\). As you can see, only the second (“old-file”) and fifth (“new-file”) arguments will be passed to the diff tool.

Note: On windows this .sh file will work with git bash.

2. Type the following into git bash / command prompt

git config --global diff.external <path_to_wrapper_script>


replacing with the path to “git-diff-wrapper.sh”, so your ~/.gitconfig contains

[diff]
external = <path_to_wrapper_script>



Be sure to use the correct syntax to specify the paths to the wrapper script and diff tool, i.e. use forward slashed instead of backslashes. In my case, I have

[diff]
external = c:/Documents and Settings/pushrbx/git-diff-wrapper.sh


in .gitconfig and
"c:/Program Files/Meld/meld.exe" "$2" "$5" | cat

in the wrapper script. Mind the trailing “cat”!

Note: I guess the ‘| cat’ is needed only for some programs which may not return a proper or consistent return status. You might want to try without the trailing cat if your diff tool has explicit return status.

Resolving the conflict

After git reports a conflict you should invoke the git mergetool command in the command prompt. This will executes the registered diff/merge tool to show the conflicting items and to let you resolve them. As soon as you have sorted them out you have to create commit to complete the merge.

Detached heads

Any checkout of a commit that is not the name of one of your branches will get you a detached HEAD. A SHA1 which represents the tip of a branch would still gives a detached HEAD. Only a checkout of a local branch name avoids that mode. For example, if you checkout a “remote branch” without tracking it first, you can end up with a detached HEAD.

Committing with a Detached HEAD

When HEAD is detached, commits work like normal, except no named branch gets updated. (You can think of this as an anonymous branch.)

Once you check out something else, say master, the commit is (presumably) no longer referenced by anything else, and gets lost. Note that after the command, there is nothing referencing 2eecb.

If, on the other hand, you want to save this state, you can create a new named branch using git checkout -b name.

Cherry picking

The cherry-pick command “copies” a commit, creating a new commit on the current branch with the same message and patch as another commit.