Tag Archives: Git

Git fix guide

I consider myself a fairly advanced user of Git.  I’ve been using it for most of the past 5 years and really enjoy the workflow improvements, and in the circles I run I am typically consulted when folks have a question about Git.  Of course, I couldn’t be nearly as effective without the help of others on the web.

For example, today I was looking for a way to remove a file from a commit that I hadn’t yet pushed, and my search led me to Seth Robertson’s page On undoing, fixing, or removing commits in git.  This page walks you through solutions by asking a series of questions about the current state of your repository and your changes.  Great guide!

Oh, and to remove a file from a commit, you can issue this command:

git reset HEAD^

which will undo the last commit, restoring your index to the previous commit, and leave the working directory with the changes uncommitted.

Git and git-flow command line completion

I switch between the command line and a GUI tool called SourceTree to manage my Git repositories.  When I first installed git-flow, I noticed that the readme talked about command line completion.  It took me a while to get around to installing it, but I have to say that it is really impressive.

Git and git-flow command line completion works like the rest of bash against the file system.  It reminds me a little of PowerShell on Windows (that should tell you how much I know about bash).

To install command line completion on a Mac, you need to find a copy of the git-completion.bash script.  Some of the posts I read on the net seem to indicate that it should be installed with Git in a contrib folder, but I couldn’t find it (showing how new I am on a Mac), so I decided to just clone Git from Git-Hub. Once I had that, I created a link to the script in my ~/bin directory:

cd ~/src/external
git clone https://github.com/git/git.git git
cd ~
mkdir bin
ln -s ~/src/external/git/contrib/completion/git-completion.bash ./git-completion.sh

I did a similar thing for git-flow-completion:

cd ~/src/external
git clone https://github.com/bobthecow/git-flow-completion.git git-flow-completion
cd ~/bin
ln -s ~/src/external/git-flow-completion/git-flow-completion.bash ./git-flow-completion.sh

The next thing to do is to modify your .bash_profile to call those scripts.

source ~/bin/git-completion.sh
source ~/bin/git-flow-completion.sh

Now when you execute git commands from the command line, you can use tab to complete commands, branches, remotes, and features. On top of that, it works for both local and remote elements. Very cool!

Resources

Collapsing commits in Git

There are times with any source control system that changes are committed after which you realize that you made a mistake and need to make a quick change.  In those cases, it seems pretty silly to keep the history for both those commits.

Git supports collapsing commits natively with the git rebase command.  This command provides more than simply collapse functionality, and the collapse functionality appears to be provided only in an interactive mode.  Paralyzed Egg has a good article on how to do this, but there was one pitfall I fell into that wasn’t described there so I’ll provide the detailed instructions here.

The first step is to look at the log to determine which commits you want to collapse:

$ git log -3
commit d43ace68df8a298d205fb422b3261d69bc5d19e4
Author: David Potter <davidp@example.com>
Date:   Sat Mar 26 15:21:29 2011 -0700

    Move README back to the root.

commit 553561e3330ea60db8a3f7baadd08ef116d22129
Author: David Potter <davidp@example.com>
Date:   Sat Mar 26 15:20:46 2011 -0700

    Move project files to the src directory.

commit 41a6af9de1f0e3fb84ae46c301a0252d0f861f02
Author: David Potter <davidp@example.com>
Date:   Tue Mar 22 11:35:14 2011 -0700

    Updated API layer

In this case I had moved one too many files into the src directory and needed to move it back. I wanted to collapse the last two commits into a single commit. To do this, I executed the following command:

$ git rebase -i HEAD~2

This will bring up your editor.

Note that git expects the editor to return when it is done. I had my EDITOR environment variable defined to launch TextMate which returns immediately, which prevents this feature from working.

The contents of the editor will look something like this:

pick 553561e Move project files to the src directory.
pick d43ace6 Move README back to the root.

# Rebase 41a6af9..d43ace6 onto 41a6af9
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x <cmd>, exec <cmd> = Run a shell command <cmd>, and stop if it fails
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

To merge those two into a single commit, change the second pick command to squash. Here is the result of that edit:

pick 553561e Move project files to the src directory.
squash d43ace6 Move README back to the root.

# Rebase 41a6af9..d43ace6 onto 41a6af9
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x <cmd>, exec <cmd> = Run a shell command <cmd>, and stop if it fails
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Save the file and exit. Git will bring up the editor with content that looks like this:

# This is a combination of 2 commits.
# The first commit's message is:

Move project files to the src directory.

# This is the 2nd commit message:

Move README back to the root.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       renamed:    74-location.png -> src/74-location.png
#       renamed:    ACS-Info.plist -> src/ACS-Info.plist
#       renamed:    ACS.xcodeproj/project.pbxproj -> src/ACS.xcodeproj/project.pbxproj
#       renamed:    ACS.xcodeproj/project.xcworkspace/contents.xcworkspacedata -> src/ACS.xcodeproj/project.xcworkspace/contents.xcworkspacedata
#       renamed:    ACS_Prefix.pch -> src/ACS_Prefix.pch
#       renamed:    Classes/ACSAppDelegate.h -> src/Classes/ACSAppDelegate.h
#       renamed:    Classes/ACSAppDelegate.m -> src/Classes/ACSAppDelegate.m
#       renamed:    Classes/BaseUrlConnection.h -> src/Classes/BaseUrlConnection.h
#       renamed:    Classes/BaseUrlConnection.m -> src/Classes/BaseUrlConnection.m
#       renamed:    Classes/InrixApi.h -> src/Classes/InrixApi.h
#       renamed:    Classes/InrixApi.m -> src/Classes/InrixApi.m
#       renamed:    Classes/MKMapView+ZoomLevel.h -> src/Classes/MKMapView+ZoomLevel.h
#       renamed:    Classes/MKMapView+ZoomLevel.m -> src/Classes/MKMapView+ZoomLevel.m
#       renamed:    Classes/Settings.h -> src/Classes/Settings.h
#       renamed:    Classes/TileOverlay.h -> src/Classes/TileOverlay.h
#       renamed:    Classes/TileOverlay.m -> src/Classes/TileOverlay.m
#       renamed:    Classes/TileOverlayView.h -> src/Classes/TileOverlayView.h
#       renamed:    Classes/TileOverlayView.m -> src/Classes/TileOverlayView.m
#       renamed:    Classes/TrafficViewController.h -> src/Classes/TrafficViewController.h
#       renamed:    Classes/TrafficViewController.m -> src/Classes/TrafficViewController.m
#       renamed:    Classes/TrafficViewController.xib -> src/Classes/TrafficViewController.xib
#       renamed:    GDataXMLNode.h -> src/GDataXMLNode.h
#       renamed:    GDataXMLNode.m -> src/GDataXMLNode.m

The instructions say that all lines beginning with a # will be ignored. That means all lines NOT beginning with a # will be included in the resulting commit message. That wasn’t clear to me when I first read that, maybe because that message is in the middle of the buffer. I changed this buffer to look like this:

# This is a combination of 2 commits.
# The first commit's message is:

Move project files to the src directory.

# This is the 2nd commit message:

#Move README back to the root.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       renamed:    74-location.png -> src/74-location.png
#       renamed:    ACS-Info.plist -> src/ACS-Info.plist
#       renamed:    ACS.xcodeproj/project.pbxproj -> src/ACS.xcodeproj/project.pbxproj
#       renamed:    ACS.xcodeproj/project.xcworkspace/contents.xcworkspacedata -> src/ACS.xcodeproj/project.xcworkspace/contents.xcworkspacedata
#       renamed:    ACS_Prefix.pch -> src/ACS_Prefix.pch
#       renamed:    Classes/ACSAppDelegate.h -> src/Classes/ACSAppDelegate.h
#       renamed:    Classes/ACSAppDelegate.m -> src/Classes/ACSAppDelegate.m
#       renamed:    Classes/BaseUrlConnection.h -> src/Classes/BaseUrlConnection.h
#       renamed:    Classes/BaseUrlConnection.m -> src/Classes/BaseUrlConnection.m
#       renamed:    Classes/InrixApi.h -> src/Classes/InrixApi.h
#       renamed:    Classes/InrixApi.m -> src/Classes/InrixApi.m
#       renamed:    Classes/MKMapView+ZoomLevel.h -> src/Classes/MKMapView+ZoomLevel.h
#       renamed:    Classes/MKMapView+ZoomLevel.m -> src/Classes/MKMapView+ZoomLevel.m
#       renamed:    Classes/Settings.h -> src/Classes/Settings.h
#       renamed:    Classes/TileOverlay.h -> src/Classes/TileOverlay.h
#       renamed:    Classes/TileOverlay.m -> src/Classes/TileOverlay.m
#       renamed:    Classes/TileOverlayView.h -> src/Classes/TileOverlayView.h
#       renamed:    Classes/TileOverlayView.m -> src/Classes/TileOverlayView.m
#       renamed:    Classes/TrafficViewController.h -> src/Classes/TrafficViewController.h
#       renamed:    Classes/TrafficViewController.m -> src/Classes/TrafficViewController.m
#       renamed:    Classes/TrafficViewController.xib -> src/Classes/TrafficViewController.xib
#       renamed:    GDataXMLNode.h -> src/GDataXMLNode.h
#       renamed:    GDataXMLNode.m -> src/GDataXMLNode.m

After saving and quitting the editor, Git prints out what it did:

[detached HEAD 419d987] Move project files to the src directory.
 26 files changed, 0 insertions(+), 0 deletions(-)
 rename 74-location.png => src/74-location.png (100%)
 rename ACS-Info.plist => src/ACS-Info.plist (100%)
 rename {ACS.xcodeproj => src/ACS.xcodeproj}/project.pbxproj (100%)
 rename {ACS.xcodeproj => src/ACS.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%)
 rename ACS_Prefix.pch => src/ACS_Prefix.pch (100%)
 rename {Classes => src/Classes}/ACSAppDelegate.h (100%)
 rename {Classes => src/Classes}/ACSAppDelegate.m (100%)
 rename {Classes => src/Classes}/BaseUrlConnection.h (100%)
 rename {Classes => src/Classes}/BaseUrlConnection.m (100%)
 rename {Classes => src/Classes}/InrixApi.h (100%)
 rename {Classes => src/Classes}/InrixApi.m (100%)
 rename {Classes => src/Classes}/MKMapView+ZoomLevel.h (100%)
 rename {Classes => src/Classes}/MKMapView+ZoomLevel.m (100%)
 rename {Classes => src/Classes}/Settings.h (100%)
 rename {Classes => src/Classes}/TileOverlay.h (100%)
 rename {Classes => src/Classes}/TileOverlay.m (100%)
 rename {Classes => src/Classes}/TileOverlayView.h (100%)
 rename {Classes => src/Classes}/TileOverlayView.m (100%)
 rename {Classes => src/Classes}/TrafficViewController.h (100%)
 rename {Classes => src/Classes}/TrafficViewController.m (100%)
 rename {Classes => src/Classes}/TrafficViewController.xib (100%)
 rename GDataXMLNode.h => src/GDataXMLNode.h (100%)
 rename GDataXMLNode.m => src/GDataXMLNode.m (100%)
 rename MainWindow.xib => src/MainWindow.xib (100%)
 rename car_256.png => src/car_256.png (100%)
 rename main.m => src/main.m (100%)
Successfully rebased and updated refs/heads/master.

Here is the result:

$ git log -2
commit 419d987ffbf2c70b1cad664aa2a56281e681268d
Author: David Potter <davidp@example.com>
Date:   Sat Mar 26 15:20:46 2011 -0700

    Move project files to the src directory.

commit 41a6af9de1f0e3fb84ae46c301a0252d0f861f02
Author: David Potter <david@example.com>
Date:   Tue Mar 22 11:35:14 2011 -0700

    Updated API layer

If conflicts occur during this process, you can resolve them and then continue the rebase, like so:

$ git rebase --continue

If you decide you don’t want to continue, you can abort the operation instead:

$ git rebase --abort

You can see the operations that have been performed on the repository using the following command:

$ git reflog --oneline

419d987 HEAD@{0}: rebase -i (squash): Move project files to the src directory.
41a6af9 HEAD@{1}: rebase -i (squash): updating HEAD
553561e HEAD@{2}: checkout: moving from master to 553561e
d43ace6 HEAD@{3}: commit: Move README back to the root.
553561e HEAD@{4}: commit: Move project files to the src directory.
41a6af9 HEAD@{5}: pull origin master: Fast-forward
ce65952 HEAD@{6}: pull origin master: Fast-forward
876b07b HEAD@{7}: pull origin master: Fast-forward
28c415e HEAD@{8}: clone: from git://server/project