Today I Learned Git Part 5

This is part 5 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.

Grep For a Pattern on Another Branch

Git has a built-in grep command that works essentially the same as the standard grep command that unix users are used to. The benefit of git-grep is that it is tightly integrated with Git. You can search for occurrences of a pattern on another branch. For example, if you have a feature branch, my-feature, on which you'd like to search for occurrences of user.last_name, then your command would look like this:

git grep 'user.last_name' my-feature

If there are matching results, they follow this format:

my-feature:app/views/users/show.html.erb:  <%= user.last_name %>
...

This formatting is handy because you can easily copy the branch and file directive for use with git-show.

See man git-grep for more details.

Grep Over Commit Messages

The git log command supports a --grep flag that allows you to do a text search (using grep, obviously) over the commit messages for that repository. For the git user that writes descriptive commit messages, this can come in quite handy. In particular, this can be put to use in an environment where the standard process involves including ticket and bug numbers in the commit message. For example, finding bug #123 can be accomplished with:

git log --grep="#123"

See man git-log for more details.

Ignore Changes to a Tracked File

Files that should never be tracked are listed in your .gitignore file. But what about if you want to ignore some local changes to a tracked file?

You can tell git to assume the file is unchanged

git update-index --assume-unchanged <some-file>

Reversing the process can be done with the --no-assume-unchanged flag.

Intent to Add

Git commands like git diff and git add --patch are awesome, but their little caveat is that they only work on files that are currently tracked in the repository. That means that after working on that new feature for 30 minutes, a git diff is only going to show you the changes to existing files and when you go to start making commits with git add --patch you will again be provided with only part of the picture.

The git add command has a flag, -N, that is described in the git man pages:

Record only the fact that the path will be added later. An entry for the path is placed in the index with no content. This is useful for, among other things, showing the unstaged content of such files with git diff and committing them with git commit -a.

By adding untracked files with the -N flag, you are stating your intent to add these files as tracked files. Once git knows that these files are meant to be tracked, it will know to include them when doing things like computing the diff, performing an interactive add, etc.

Interactively Unstage Changes

I often use git add --patch to interactively stage changes for a commit. Git takes me through changes to tracked files piece by piece to check if I want to stage them. This same interactive staging of files can be used in reverse when removing changes from the index. Just add the --patch flag.

You can use it for a single file

git reset --patch README.md

or you can let it operate on the entire repository

git reset --patch

This is useful when you've staged part of a file for a commit and then realize that some of those changes shouldn't be committed.

Last Commit a File Appeared

In my project, I have a README.md file that I haven't modified in a while. I'd like to take a look at the last commit that modified it. The git log command can be used here with few arguments to narrow it down.

git log -1 -- README.md
commit 6da76838549a43aa578604f8d0eee7f6dbf44168
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sun May 17 12:08:02 2015 -0500

    Add some documentation on configuring basic auth.

This same command will even work for files that have been deleted if you know the path and name of the file in question. For instance, I used to have an ideas.md file and I'd like to find the commit that removed it.

git log -1 -- docs/ideas.md
commit 0bb1d80ea8123dd12c305394e61ae27bdb706434
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sat May 16 14:53:57 2015 -0500

    Remove the ideas doc, it isn't needed anymore.

List All Files Changed Between Two Branches

The git-diff command can help with finding all files that have changed between two branches. For instance, if you are at the HEAD of your current feature branch and you'd like to see the list of files that have changed since being in sync with the master branch, you'd formulate a command like the following:

git diff --name-only master

The --name-only flag is important because it cuts out the rest of the non-essential output.

You'll want to list the older branch first and the current branch second as a way of showing what has changed from the original branch to get to the current branch.

Though the example shows the use of branches any git reference can be used to see what has changed between two commits.

See man git-diff for more details.

List Different Commits Between Two Branches

There are times when I want to get a sense of the difference between two branches. I don't want to look at the actual diff though, I just want to see what commits are on one and the other.

I can do just this by using the git-log command with a couple flags, most importantly the --cherry-pick flag.

To compare the feature branch against the master branch, I can run a command like the following:

git log --left-right --graph --cherry-pick --oneline feature...branch

This lists commits with the first line of their messages. It also includes either a < or > arrow at the front of each commit indicating whether the commit is on the left (feature) or right (master) branch, respectively.

Note: you can use more than branches in a command like this. Any two references will work. You can just use two SHAs for instance.

List Filenames without the Diffs

The git show command will list all changes for a given reference including the diffs. With diffs included, this can get rather verbose at times. If you just want to see the list of files involved (excluding the diffs), you can use the --name-only flag. For instance,

git show HEAD --name-only
commit c563bafb511bb984c4466763db7e8937e7c3a509
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sat May 16 20:56:07 2015 -0500

    This is my sweet commit message

app/models/user.rb
README.md
spec/factories/user.rb

List Most Git Commands

You can list most git commands by using the -a flag with git-help:

git help -a

List Untracked Files

Git's ls-files command is a plumbing command that allows you to list different sets of files based on the state of the working directory. So, if you want to list all untracked files, you can do:

git ls-files --others

This includes files ignored by the .gitignore file. If you want to exclude the ignored files and only see untracked and unignored files, then you can do:

git ls-files --exclude-standard --others

There are some other flags worth checking at at git help ls-files.