Today I Learned Git Part 6

This is part 6 to my series of posts about learning git. Most of the sections here are from various TILs I've found and hand picked ones I found useful.

Move The Latest Commit To A New Branch

I sometimes find myself making a commit against the master branch that I intended to make on a new branch. To get this commit on a new branch, one possible approach is to do a reset, checkout a new branch, and then re-commit it. There is a better way.

git checkout -b my-new-branch
git checkout - 
git reset --hard HEAD~

This makes better use of branches and avoids the need to redo a commit that has already been made.

Note: The example was against the master branch, but can work for any branch.

Reference a Commit Via Commit Message Pattern Matching

Generally when referencing a commit, you'll use the SHA or a portion of the SHA. For example with git-show:

git show cd6a63d014
...

There are many ways to reference commits though. One way is via regex pattern matching on the commit message. For instance, if you recently had a commit with a typo and you had included typo in the commit message, then you could reference that commit like so:

git show :/typo
Author: Josh Branchaud
Date: Mon Dec 21 15:50:20 2015 -0600

    Fix a typo in the documentation
...

By using :/ followed by some text, git will attempt to find the most recent commit whose commit message matches the text. As I alluded to, regex can be used in the text.

See $ man gitrevisions for more details and other ways to reference commits.

Rename a Remote

If you just added a remote (git remote add ...) and messed up the name or just need to rename some existing remote, you can do so with the rename command.

First, let's see the remotes we have:

git remote -v
origin  https://github.com/jbranchaud/til.git (fetch)
origin  https://github.com/jbranchaud/til.git (push)

To then rename origin to destination, for example, we can issue the following command:

git remote rename origin destination

See man git-remote for more details.

Renaming A Branch

The -m flag can be used with git branch to move/rename an existing branch. If you are already on the branch that you want to rename, all you need to do is provide the new branch name.

git branch -m <new-branch-name>

If you want to rename a branch other than the one you are currently on, you must specify both the existing (old) branch name and the new branch name.

git branch -m <old-branch-name> <new-branch-name>

Resetting a Reset

Sometimes we run commands like git reset --hard HEAD~ when we shouldn't have. We wish we could undo what we've done, but the commit we've reset is gone forever. Or is it?

When bad things happen, git-reflog can often lend a hand. Using git-reflog, we can find our way back to were we've been; to better times.

git reflog
00f77eb HEAD@{0}: reset: moving to HEAD~
9b2fb39 HEAD@{1}: commit: Add this set of important changes
...

We can see that HEAD@{1} references a time and place before we destroyed our last commit. Let's fix things by resetting to that.

git reset HEAD@{1}

Our lost commit is found.

Unfortunately, we cannot undo all the bad in the world. Any changes to tracked files will be irreparably lost.

Show All Commits for a File Beyond Renaming

By including -- <filename> with a git log command, we can list all the commits for a file. The following is an example of such a command with some formatting and file names.

git log --name-only --pretty=format:%H -- README.md
4e57c5d46637286731dc7fbb1e16330f1f3b2b7c
README.md

56955ff027f02b57212476e142a97ce2b7e60efe
README.md

5abdc5106529dd246450b381f621fa1b05808830
README.md

What we may not realize, though, is that we are missing out on a commit in this file's history. At one point, this file was renamed. The command above wasn't able to capture that.

Using the --follow flag with a file name, we can list all commits for a file beyond renaming.

git log --name-only --pretty=format:%H --follow README.md
4e57c5d46637286731dc7fbb1e16330f1f3b2b7c
README.md

56955ff027f02b57212476e142a97ce2b7e60efe
README.md

5abdc5106529dd246450b381f621fa1b05808830
README.md

ea885f458b0d525f673623f2440de9556954c0c9
README.rdoc

This command roped in a commit from when README.md used to be called README.rdoc. If you want to know about the full history of a file, this is the way to go.

Show the diffstat Summary of a Commit

Use the --stat flag when running git show on a commit to see the diffstat summary of that commit. For instance, this is what I get for a recent commit to delve:

git show 8a1f36a1ce --stat
commit 8a1f36a1ce71d708d1d82afbc2191de9aefba021
Author: Derek Parker <derek.parker@coreos.com>
Date:   Wed Jan 27 23:47:04 2016 -0800

    dlv: Flag to print stacktrace on trace subcommand

 cmd/dlv/main.go     | 45 ++++++++++-----------------------------------
 terminal/command.go |  7 +++++--
 2 files changed, 15 insertions(+), 37 deletions(-)

The following is a description of the diffstat program

This program reads the output of diff and displays a histogram of the insertions, deletions, and modifications per-file.

See man git-show and man diffstat for more details.

Single Key Presses in Interactive Mode

When staging changes in interactive mode (git add -p), you have a number of options associated with single keys. y is yes, n is no, a is this and all remaining, and so on.

By default, you have to press enter after making your selection. However, it can be configured for single key presses. Add the following to your git configuration file to enable single key presses for interactive mode:

[interactive]
    singlekey = true

Staging Changes within Vim

I've always used git from the command line, but only recently have I started to leverage fugitive.vim to more quickly do some common git commands from within vim.

I mostly use fugitive to stage changes for committing. To stage entire files, you can view the repository status, :Gstatus, and then navigate up and down (k and j) tapping - next to the files to be staged (or unstaged).

I've started to use git's interactive mode for staging changes from the command line (git add --patch) more and more and recently wondered if the same thing can be accomplished with fugitive.

It turns out it's pretty simple to do so. Instead of tapping - next to a file you want to stage, you can tap p next to it and you will be immediately dropped into interactive mode for that file. Prepare the lines you want to stage (or, again, unstage) and save.