Git merge commit with no fast forward

When working in open source, it is quite common that I want to either manually merge in a pull request or refer to a Github issue when merging in one of my own branches. If you do a merge and there are no merge conflicts then Git will not create a commit. It does a fast-forward merge as this is the desired behaviour most of the time.

To create a merge commit when merge in a branch, I use this command:

git merge branchname --no-ff -m "Merge pull request #999 from fork/branch"

The –no-ff switch means no fast-forward while merging. There is some Github functionality in the commit message. Using # and the issue or pull request number will create an automatic link to this commit in the issue or pull request.

Github PR
Commit reference as seen in a Github Pull Request

A few tips on git reset

I’ve learned a couple of handy tricks with git reset during the last few weeks (mostly by following Github employees on Twitter!).

This first tip is really handy when you have messed up your master branch by forgetting to create a new branch or when you want to redo your commits. For example, if you want to submit a beautiful, handcrafted pull request but your recent commit history looks really nasty then run this command:

git reset --soft origin/master

This will rollback your branch to the same commit as origin/master but it keeps all the changes you have done. So the commits are removed but all the files you have changed are moved to staging. You can then unstage and stage files and handcraft logical commits with fantastic commit messages. Don’t forget to do a git fetch origin/master first and a git rebase afterwards!

The 3 modes

There are three modes for git reset: soft, mixed and hard. See this post on ProGit for an in-depth explanation. If I had used –mixed instead of –soft then I would also have unstaged all my changes. While –hard would have thrown away all my changes and my local repo would look exactly like origin/master. So be careful with hard resets as the only way you might be able to get your changes back is using something like git reflog.

The Last Trick

git reset will take any git reference (SHA) or treeish.  Examples of treeishes are:

  • origin/master – which is a remote branch name
  • HEAD~ – the previous commit of wherever HEAD is pointing
  • master~2, master^^ – two commits back from the current master HEAD
  • master@{5} – 5 commits back from current master HEAD
  • 981c1cc775845200e6cb5ad6e9d8d10f2d3e1cf8 – a complete SHA
  • 981c1cc775845200e – a partial SHA

To quickly reset your branch HEAD to a previous commit, choose an appropriate treeish for a previous commit and pass it into git reset. Here is an example using a complete SHA which I found by using git log to see my recent commits:

git reset --hard eff383a443e8bfe1859063079bd15f712509e2c5

Git for Windows tip: How to copy and paste into Bash

Per default the only way to copy and paste into the Git Bash is to click on the git icon in the top left corner and select Edit->Mark/Copy/Paste. This is actually not a property of Git Bash. It is just how the Windows console runner works i.e. the standard command prompt.

There are two solutions to be able to copy/paste with the keyboard and directly with the mouse. The first is to use a different console, see Scott Hanselman’s post on Console2. I haven’t tried this yet so let me know in the comments if you use Console2 and can recommend it.

The other solution is to enable QuickEdit Mode under Options->Edit Options in the properties for the console. To open the Properties dialog, click the Git icon in the top left corner of the console and choose Properties in the menu.

msysgitPropertiesCapture

Now you can select text with the mouse and right click to copy. Pasting is done by either right clicking with the mouse or pressing the Insert key. Still more awkward than using Ctrl-C and Crtl-V so maybe it is time to check out Console2.

Git for Windows tip: Use P4Merge as mergetool

I recently found P4Merge (thank you Twitter and Git Immersion) and instantly dropped WinMerge as my standard diff/merge tool. I really like the way it visualises the differences and the 3-way merge is really nicely done. P4Merge is the merge tool for Perforce (which I have never used) and is both free and can be downloaded separately from the rest of Perforce.

The download can be found here. Choose Browse by component->Clients->Visual Merge Tool as you do not want to download the whole Perforce client package. In the installer for P4Merge you can choose which components you wish to install, you only need the Visual Merge Tool (P4Merge).

Install p4merge and then set it as your merge tool for git by running the following two config commands:

git config --global merge.tool p4merge
git config --global mergetool.p4merge.cmd 'p4merge.exe \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"'

EDIT: As pointed out by barik in the comments, in newer versions of git this works as well and is slightly simpler:

git config --global merge.tool p4merge
git config --global mergetool.p4merge.path "C:/Program Files/Perforce/p4merge.exe"

P4Merge can be used for doing diffs and merges. If using P4Merge for diffs then call:

git difftool

If the file you want to compare is already staged then use the –cached switch after that command.

When Git tells you that there has been conflict, to resolve it type:

git mergetool

This will open P4Merge and show three different versions of the file; your local version, the version you are trying to merge in (probably the master branch) and the base version. The base version is the common ancestor of the local version and the remote version. Choose which version wins or edit the merge manually and then save and quit P4Merge. Finally commit the merge and then remove any .orig files that may be left over. It should be possible to have git remove the .orig file automatically by setting mergetool.keepBackup to false in git config but I have not succeeded in getting that to work for me yet.

An example of using P4Merge

And this is what a 3-way merge looks like. The local version on the left, the base version in the middle and the remote version (the master branch) on the right. In the merge window at the bottom all three versions are currently selected. To select the local version, for example, I would click the blue diamond icon and then save and quit.

p4mergeCapture

Git for Windows tip: setting an editor

The first time I tried to do git commit on msysgit with no commit message (no –m switch), it opened up Vim so that I could write my commit message. Luckily for me I’d read enough Vim jokes on Twitter to know that :q or :q! would get me out. But there is no way I’d be able to actually write a commit message, save and quit in Vim (that’d probably take about 3 weeks of studying the documentation). Much easier to change the editor for git to Notepad++ or some other more familiar text editor.

The first step is to create a bat file (I called mine npp.bat) with the path to Notepad++ and the appropriate switches:

#!/bin/sh
"C:/Program Files (x86)/Notepad++/notepad++.exe" -multiInst -notabbar -nosession -noPlugin "$*"

This opens a new instance of Notepad++ with no sessions or tabs or plugins which is what I want when writing a commit message. Place the bat file in the git subdirectory of msysgit (c:\msysgit\git on my system) and then set the core.editor variable in git config:

git config --global core.editor c:/msysgit/git/npp.bat

See this answer on Stackoverflow for more variations on this.

The next step is to get Notepad++ to work from the command line. I want to be able to write:

notepad .bashrc

and Notepad++ should open the .bashrc file. This is actually nothing to do with msysgit and involves replacing the default notepad application with Notepad++ in Windows. And the easiest way is to use a program called Notepad Replacer. Install this and voila, it now works. If you ever want to revert to the default notepad then just uninstall Notepad Replacer.

The other option is create a script file just like the npp.bat file but without the switches and name it npp with no extension and place it in the bin folder. Now I can write:

npp .bashrc

and it opens the file in Notepad++. This approach also works for any third party editor such as Word or Excel.

EDIT: An alternative way to achieve the same result is to install Gitpad to change the default editor to Notepad (or the default text editor).

Git for Windows tip: Setting shell aliases with msysgit

As msysgit uses a bash shell, you can set really handy aliases for the different git commands. For example gs instead of git status and ga instead of git add are the ones I use the most. Check out the great Git Immersion tutorial from Edgecase for a list of aliases. I can also recommend creating the got and get aliases. It’s amazing how often I write got instead of git.

Using the bash shell can be unfamiliar to a Windows user. So if you want to set aliases in msysgit it is not very obvious where they should go. They could go into the .bashrc file or the bash_profile file. From a linux perspective the bash_profile configuration file is executed when logging in while the .bashrc file is executed every time a bash shell window is opened. The .bashrc file is located in the c:\Users\YourUsername directory and the bash_profile file is in the etc directory. In the case of msysgit I fail to see that there is any real practical difference; both are executed when you start up msysgit. So I just used .bashrc and it works great.

If there is no file named .bashrc then you’ll have to create it. Windows 7 will not allow you to create a file with a dot (period) as the first character in the filename and with no extension. So the easy way to do this is to name your file .bashrc. (with an extra dot at the end) and Windows will automatically rename the file to just .bashrc (with no dot at the end).

My .bashrc file (almost identical to the Git Immersion profile file that is linked above):

alias less='less -r'
# --show-control-chars: help showing Korean or accented characters
alias ls='ls -F --color --show-control-chars'
alias ll='ls -l'
alias gs='git status '
alias ga='git add '
alias gb='git branch '
alias gc='git commit'
alias gd='git diff'
alias go='git checkout '
alias gk='gitk --all&'
alias gx='gitx --all'
alias got='git '
alias get='git '