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.
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.
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
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.
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 havein .gitconfig and
[diff] external = c:/Documents and Settings/pushrbx/git-diff-wrapper.shin the wrapper script. Mind the trailing “cat”!
"c:/Program Files/Meld/meld.exe" "$2" "$5" | 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
catif 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.
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
If, on the other hand, you want to save this state, you can create a new named branch using git checkout -b name.
The cherry-pick command “copies” a commit, creating a new commit on the current branch with the same message and patch as another commit.