Today I Learned

A Hashrocket project

77 posts about #git

Pulling/fetching tags

Git does not always pull the latest tags, if you have a conflict trying to push a new tag, you'll see a message like

! [rejected]  v1.4.7 -> v1.4.7 (already exists)

git fetch will not always help you if the tagged head is detached try git pull --tags.

Create a github repository from the cmd line

In the effort to never leave the command line anything to do with github is always a frustration. The hub command line tool - a tool that augments git with github specific commands - helps out tremendously.

In this case I want to create a github repository to push my code to without opening a browser, going to github, creating a repo, copying the remote address for that repo and then setting up the remote locally.

It can all be done with one step.

hub create "exciting_open_source_tech_repo"

git init must be run first, so that there is a local repo to link to the github repo, but then your good. Start modifying, adding and commiting as you normally would.

Mass-Delete Git Tags

Building off this post:

I'm an advocate of Semantic Version tagging. It communicates to a team about every deploy and makes that rare rollback easier. So when does it not make sense to use a tag?

When you're the only developer (nobody to communicate with except yourself), and also using a platform like Heroku that tags every release (your tags are redundant). This the case with my blog, so today I set out to delete all my Git tags.

First, delete them remotely (assuming a remote named origin):

$ git tag | xargs git push --delete origin

We also have to delete our local tags, or a tag push with create them again on the remote:

$ git tag | xargs git tag -d

$ git tag now returns nothing, and there are no remote tags.

Cherry Pick A Range Of Commits

Git's cherry-pick command allows you to specify a range of commits to be cherry picked onto the current branch. This can be done with the A..B style syntax -- where A is the older end of the range.

Consider a scenario with the following chain of commits: A - B - C - D.

$ git cherry-pick B..D

This will cherry pick commits C and D onto HEAD. This is because the lower-bound is exclusive. If you'd like to include B as well. Try the following:

$ git cherry-pick B^..D

See man git-cherry-pick for more details.

When Was This File Added to Version Control?

I learned a helpful Git command today. To determine when a file was added to version control, run the following:

$ git log --follow --diff-filter=A <filename>

This returns the Git history, following files through renames, filtering on when a file was added to version control. Here's the output for the README of 'Today I Learned':

$ git log --follow --diff-filter=A README.md
commit 9a7984a48db19489bb4113378298ddaa97121c7a
Author: Jake Worth <dev+jwworth@hashrocket.com>
Date:   Sat Mar 28 12:52:19 2015 -0400

    Add a basic README

Useful for explaining mystery files in the repository 🔎.

Update The URL Of A Remote

I just changed the name of a Github repository. One of the implications of this is that the remote URL that my local git repository has on record is now out of date. I need to update it.

If I use git-remote with the -v flag. I can see what remotes I currently have.

$ git remote -v
origin  git@github.com:jbranchaud/pokemon.git (fetch)
origin  git@github.com:jbranchaud/pokemon.git (push)

Now, to update the URL for that remote, I can use git remote set-url specifying the name of the remote and the updated URL.

$ git remote set-url origin git@github.com:jbranchaud/pokemon_deluxe.git

If I check again, I can see it has been updated accordingly.

$ git remote -v
origin  git@github.com:jbranchaud/pokemon_deluxe.git (fetch)
origin  git@github.com:jbranchaud/pokemon_deluxe.git (push)

Accept Your Own Changes During Git Rebase

During a git rebase you may encounter conflicts in files between your current, HEAD, branch and the branch you're rebasing. Ordinarily, you'll want to go through each file and individually resolve each conflict to ensure that the proper changes are preserved.

Sometimes, however, you already know that you want to accept ALL the changes in a file on your local branch and discard the other branch's edits. Instead of opening the file, finding the conflict regions, then making the appropriate changes, you can more succinctly prefer your changes with the following command:

git checkout --ours /path/to/file.js

Conversely, if you want to keep the other branch's changes, run:

git checkout --theirs /path/to/file.ex

You can also do this for an entire directory:

git checkout --ours /path/to/dir/
git checkout --theirs . # Current working directory

When you [re]open the conflict files, you'll see that your preferred branch's changes have been written and the other branch's have been discarded.

After you've finished, stage the the conflict files, and continue your rebase:

git add /path/to/conflict_file.rb
git rebase --continue # or --skip

Show commits in the order they were committed

When reviewing a series of commits its sometimes helpful to review them in the order they were committed.

By default, if you pass a range to git show, like git show HEAD..master (assuming you are on a different branch and master is ahead by a number of commits) the commits will be shown in the order of most recent to least recent.

To view the commits in least recent to most recent order use the --reverse flag:

git show HEAD..master --reverse

Submit a pull request from the command line

Github offers a git extension for augmenting the git command line tool with github specific functionality. This extension is called hub.

hub adds the git alias pull-request to git. This alias/command creates a pull request from the current branch to master by default:

git pull-request

Additionally, you can specify which branch would be the base with the first argument and which branch would be the head with the second argument, like so:

git pull-request devolop feature/add_colors

Use git subtree to merge repositories with history

Use git subtree to merge repositories with history. For example, the following command merges the master branch of repository foo into the directory foo on the current repository.

git subtree add -P foo /path/to/foo master

See https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt for more details.

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.

Viewing A File On Another Branch

Sometimes you want to view a file on another branch (without switching branches). That is, you want to view the version of that file as it exists on that branch. git show can help. If your branch is named my_feature and the file you want to see is app/models/users.rb, then your command should look like this:

$ git show my_feature:app/models/users.rb

You can even tab-complete the filename as you type it out.

See man git-show for more details.

source

Git Garbage Collection - optimize local repository

As you work with git a lot of garbage gets accumulated in your .git folder such as file revisions and unreachable objects.

On large repositories and long running projects this negatively affects both operating performance and disk space utilization.

To clean up the garbage git provides a command:

git gc

This will not impact the remote repository and will only optimize the local copy so it is safe to run on any git repo you might have. (In fact this operation is already run for you automatically after some git commands that leave behind too many loose objects)

If you want a deeper cleaning (which you should every few hunderd changesets or so), run:

git gc --aggressive

This will take longer but will provide better results.

To learn more:

git help gc

What Changed?

If you want to know what has changed at each commit in your Git history, then just ask git whatchanged.

$ git whatchanged

commit ddc929c03f5d629af6e725b690f1a4d2804bc2e5
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sun Feb 12 14:04:12 2017 -0600

    Add the source to the latest til

:100644 100644 f6e7638... 2b192e1... M  elixir/compute-md5-digest-of-a-string.md

commit 65ecb9f01876bb1a7c2530c0df888f45f5a11cbb
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sat Feb 11 18:34:25 2017 -0600

    Add Compute md5 Digest Of A String as an Elixir til

:100644 100644 5af3ca2... 7e4794f... M  README.md
:000000 100644 0000000... f6e7638... A  elixir/compute-md5-digest-of-a-string.md

...

This is an old command that is mostly equivalent to git-log. In fact, the man page for git-whatchanged says:

New users are encouraged to use git-log(1) instead.

The difference is that git-whatchanged shows you the changed files in their raw format which can be useful if you know what you are looking for.

See man git-whatchanged for more details.

Clean untracked files in Git

Given I am a developer
And I am working on a new branch in an existing project
And during one of my commits I introduced a few files/folders
And those files/folders are untracked (in .gitignore)
And those files/folders are automatically generated (e.g. node_modules/ webpack_bundle.js)
When I switch back to the main branch
Then I see those files
And I don't want to...

If you find yourself in the above situation, you may want to clean your untracked files. Git provides a command for that: git clean.

This command comes with a way to see which files/folders are going to be deleted (DRY RUN):

git clean -n

You may notice that the above command does not show any untracked directories. To add directories to that list use the -d switch:

git clean -dn

Alternatively you may choose to only remove files/dirs that are in .gitignore with the -X option:

git clean -X -dn

If you are ready to take action use the -f switch and remove the -n switch:

git clean -fd

Filter Your Git Diffs

Sometimes reading a git diff can be a big task. Imagine working through a big file cleanup, removing and modifing hundreds of files, and one of those modifications had an undesireable side effect. How can we filter the noise to find the problem?

git diff has a --diff-filter flag for this purpose. The specific command I used today was:

$ git diff --diff-filter=M HEAD~5 > changes.txt

This showed only modified files over the previous five commits, excluding thousands of deleted references. By directing the output to a file and visually scanning, I quickly found the problem— a forced redirect to HTTPS on the development server.

See git diff --help for more info.

:Gbrowse!

One of my favorite developer-to-developer communication tools is :Gbrowse, provided by vim-fugitive. This command opens your current Vim buffer, or a line or range, in the hosting provider specified by your remote. It's irreplaceable for quickly sharing code worthy of discussion.

Take this command to the next level with :Gbrowse! which puts the URL your browser would open into your paste buffer. From there, paste away into your project management tool or chat client, getting everybody on the same (web)page.

Show Changes, Excluding a Directory

This is a command we figured out recently that I'm proud of:

$ git diff --name-only new-branch..master  -- . ':!spec'

This printed a list of all the files that differed between our new-branch and master, excluding changes in the spec directory. From here you can check out the files in either state and explore.

Commit Message Templates

We set something up on a recent project that I really liked.

In the midst of writing a bunch of tests, I realized I was starting every commit message with Test, followed by a description of the feature. Test login, Test that bots are redirected, etc. Typing the same word over and over is redundant, and it adds up quickly.

We paused and set up a commit template for the project.

Here's the file we made (~/.git_commit_template.txt):

Test 

And the command in the project directory:

$ git config commit.template "~/.git_commit_template.txt"

Our next commit message started with Test, so we just used CTRL-A to jump to the end of the sentence and start filling it in.

This does more than just save keystrokes. It reminds us that we have a format we want to stick to, and helps keep our commit message more uniform.

This could also be set globally, if you have a format you prefer to use on every project.

git difftool

You've likely used git diff to see changes between your commits and your working tree, etc, but you can also use git difftool to open up the diff in your editor. In vim, this will open the original file in one pane, and the diff in another.

May require some config.

Checking Commit Ancestry

I have two commit shas and I want to know if the first is an ancestor of the second. Put another way, is this first commit somewhere in the history of this other commit.

Git's merge-base command combined with the --is-ancestor flag makes answering this question easy. Furthermore, because it is a plumbing command, it can be used in a script or sequence of commands as a switch based on the answer.

Here is an example of this command in action:

$ git merge-base --is-ancestor head~ head && echo 'yes, it is'
yes, it is
$ git merge-base --is-ancestor head~ head~~ && echo 'yes, it is'

In the first command, head~ is clearly an ancestor of head, so the echo command is triggered. In the second, head~ is not an ancestor of head~~ so the return status of 1 short-circuits the rest of the command. Hence, no echo.

See man git-merge-base for more details.

source

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.

source

You Can Git Push to an SSH Alias

When we are using Git via ssh, we can take full advandage of our ssh conveniences.

So, lets say we have some ssh coniguration in our .ssh/config

Host til-production
  Hostname 11.22.333.44
  User ubuntu

Now we ssh into our server and create a bare repository.

ssh til-production
mkdir ~/til
cd ~/til
git init --bare

Then back on our local machine, instead of adding a remote with:

git remote add production ubuntu@11.22.333.44:til

we can simply:

git remote add production til-production:til

It's a little bit less to think about.

h/t @vidalekechukwu

Clean your Working Tree, Interactively

git clean is a great command. It's the best tool in the box for wiping out untracked junk files from your working tree.

This command has a noteworthy optional flag, -i, for interactive mode. This gives you the option to clean everything, select by number, ask each, and more. I see it as the reverse of git add -p— we get granular control over what stays and what goes.

More context with git diff

So if you want to see more (or less) context when running a git diff command you can use -U<number_of_lines>.

Here it is a regular git diff (3 lines as default):

vnegrisolo@home:~/my-app(master)% git diff
index modified: app/jobs/github/sync_organization_job.rb
@ sync_organization_job.rb:24 @ module Github
      organization.login       = data['login']
      organization.description = data['description']
      organization.avatar_url  = data['avatar_url']
-      organization.user        = @user
+      organization.user        = user

      organization.save
      recursive_sync(organization) if @recursive

Now just showing the changed lines:

vnegrisolo@home:~/my-app(master)% git diff -U0
index modified: app/jobs/github/sync_organization_job.rb
@ sync_organization_job.rb:27 @ module Github
-      organization.user        = @user
+      organization.user        = user

Finally with 5 lines of context instead of 3:

vnegrisolo@home:~/my-app(master)% git diff -U5
index modified: app/jobs/github/sync_organization_job.rb
@ sync_organization_job.rb:22 @ module Github
      organization = Organization.find_or_initialize_by(github_id: data['id'])

      organization.login       = data['login']
      organization.description = data['description']
      organization.avatar_url  = data['avatar_url']
-      organization.user        = @user
+      organization.user        = user

      organization.save
      recursive_sync(organization) if @recursive
      organization
    end

Diffing With Patience

The default diff algorithm used by Git is pretty good, but it can get mislead by larger, complex changesets. The result is a noisier, misaligned diff output.

If you'd like a diff that is generally a bit cleaner and can afford a little slow down (you probably can), you can instead use the patience algorithm which is described as such:

Patience Diff, instead, focuses its energy on the low-frequency high-content lines which serve as markers or signatures of important content in the text. It is still an LCS-based diff at its core, but with an important difference, as it only considers the longest common subsequence of the signature lines:

Find all lines which occur exactly once on both sides, then do longest common subsequence on those lines, matching them up.

You can set this as the default algorithm by adding the following lines to your ~/.gitconfig file:

[diff]
    algorithm = patience

or it can be set from the command line with:

$ git config --global diff.algorithm patience

source

h/t Josh Davey

Git log => turbo mode

Today I learned that I can combine -S 'my-search' and -p in git log command. Here is an example:

git log -p -S 'create_or' app/models/*.rb

command dissecting:

  • -p => equivalent to git diff for each commit found on log
  • -S 'my-seach' => filtering by commits that add/remove this content

Tag a Commit

I ❤️ Git tags. But tagging is a manual process, and sometimes I forget to do it.

My old solution was to find the commit I deployed, check it out, tag it, and push the tags to the internet. There is a better way!

You can tag any commit from anywhere in the tree:

$ git tag v1.0.0 a795622
$ git push --tags

This will tag commit a795622 and push it to Github. A sample workflow, if your deploy was one commit in the past, would be:

$ git tag v1.0.0 HEAD~1

List local branches sorted by most recent commit

If you have a lot of local branches in your repo and you want to sort them by most recent commits:

git for-each-ref --sort=-committerdate refs/heads/

Source

Execute a shell command on every commit in rebase

Git rebase allows you to execute a command on any commit in a rebase:

git rebase -i -exec "git reset-authors"

The result will look something like this:

pick xxxyyy Do stuff to things
exec git reset-authors
pick yyyzzz Do things to stuff
exec git reset-authors

It is very useful for resetting authors on multiple commits in case you forgot to set the current pair.

P.S. if you are curious about what git reset-authors does check out our .gitconfig in Hashrocket's dotfiles:

[alias]
reset-authors = commit --amend --reset-author -CHEAD

https://github.com/hashrocket/dotmatrix/blob/master/.gitconfig#L18

Open a Pull Request from the Command Line

Do you open source? If so, then check out hub. It enhances various Git commands, making OSS contribution and project management a lot easier.

Today I learned how to open a pull request from the command line. This is the base command:

$ git pull-request

This opens an editor where you can add more information, and see your branch and your target branch. Change the target branch with the -b flag.

Once installed, get more info here:

man hub

Git list changes on files

If you want to list all changes to a file you can run:

git log -p app/controllers/training_*

h/t @greis

Was that branch merged into master?

To see if a branch was merged into master, use the --merged flag.

git branch --merged master

This will show all the existing branches that have been merged into master.

How long have I been working on this Project?

Today I was thinking back on my time on a project, and wanted to see how long I'd been assigned to it. We've been on this project for a while, so traversing the Git log would require precision.

My solution:

$ git log --author="\(jwworth\)" --reverse

We use go-pear to set Git authorship for pairs; as a result my username 'jwworth' could be in several locations in the text of the author email. Luckily, the --author flag matches regular expressions.

Global git hooks

There are no global git hooks.

But you can create global hook templates and for every project you can use them.

Create a folder for your global templates:

mkdir -p ~/.git-templates/hooks

Configure git to use this templates folder:

git config --global init.templatedir '~/.git-templates'

Create an executable hook, for example `~/.git-templates/hooks/pre-commit

#!/bin/sh
echo "This happens before the commit"

For every project you want to use these templates restart git project running:

git init

These will create symbol links like:

lrwxr-xr-x   pre-commit -> /Users/vnegrisolo/.git-templates/hooks/pre-commit

Save harddrive space by using git shallow clones

Sometime you clone starter projects, libraries, or plugins to help you get started on a new project or POC.

Perhaps you are using git clone to get your project on a remote server.

When you clone a project what you also get is all the previous states of that repository at each commit. This can add up fast and take a good chunk of your hard drive space as well as take long to download and decompress.

To solve this you can clone a shallow copy of the repository by using the depth switch on the git clone command, and providing it with the value of 1.

git clone --depth 1 https://github.com/dkarter/bullets.vim

Navigate Github Faster

On any Github repo page, typing gc will load the 'Code' tab, gi will load the 'Issues' tab, and gp will load the 'Pull requests' tab.

Fixing a past commit

To add some code changes to a past commit you can commit with the fixup option and then rebase it.

# add the code changes you want to add to the past commit
git add .

# get the git commit you want to add the changes and copy its reference
git log

# commit the changes with the options `fixup`
git commit --fixup <COMMIT_REF>

# rebase the commits to apply the fixup from one commit before the starter commit
git rebase -i --autosquash <COMMIT_REF>~1

This will open your editor (vim) with the rebase plan, so you can apply it.

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.

source

Delete Remote Git Tags

Tagging releases with Git is a good idea. In case your tags get off track, here is how you delete a Git tag locally and on a remote:

$ git tag -d abc
$ git push origin :refs/tags/abc
To git@github.com:hashrocket/hr-til
 - [deleted]         abc

It gets trickier if you're using Semantic Versioning, which includes dots in the tag name. The above won't work for v16.0.0. This will:

$ git tag -d v16.0.0
$ git push origin :v16.0.0
To git@github.com:hashrocket/hr-til
 - [deleted]         v16.0.0

Git Log With Authors

In my never-ending quest to better summarize my work at the end of the day using computers, I discovered today the Git --author flag. It works like this:

$ glg --since=midnight --author=dev+jwworth+mikechau@hashrocket.com
* 4ba91a8 (HEAD, origin/checkout, checkout) Add guard for manual entry of employee discount
* 3a4e4c9 Seed a coupon and code and auto-apply in preview
* cb1adee Add discount
...

The alias glg is discussed here.

I use this when multiple developers or teams are committing throughout the day to the same repository, to disambiguate our work from others. Ready to paste into your billing software of choice.

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.

Checkout a Pull Request

Today I learned how to checkout and inspect a Git pull request locally.

Open the .git/config file in the root of the project directory. Locate the [remote "origin"] section, and add this line:

fetch = +refs/pull/*/head:refs/remotes/origin/pr/*

Fetch the changes, and you should receive the pull request references:

From github.com:hashrocket/hr-til
 * [new ref]         refs/pull/1/head -> origin/pr/1
 * [new ref]         refs/pull/10/head -> origin/pr/10
 * [new ref]         refs/pull/11/head -> origin/pr/11
...

From here, you can checkout any pull request with git checkout pr/1, git checkout pr/10, etc.

Source: https://gist.github.com/piscisaureus/3342247

Untrack A Directory Of Files Without Deleting

In Untrack A File Without Deleting It, I explained how a specific file can be removed from tracking without actually deleting the file from the local file system. The same can be done for a directory of files that you don't want tracked. Just use the -r flag:

$ git rm --cached -r <directory>

source

Reference A Commit Via Commit Msg 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.

Source

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.

source

What Is The Current Branch?

This question can be answered with one of git's plumbing commands, rev-parse.

$ git rev-parse --abbrev-ref HEAD

The --abbrev-ref flag tells git-rev-parse to give us the short name for HEAD instead of the SHA.

source

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.

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.

Git Diff Numstat

Ever wanted to know how many lines have been added and/or removed by you current Git diff? There's a built-in command for just that:

$ git diff --numstat
28      1       README.md

My current changes add 28 lines and remove 1 line from the README.

How to remove newly git ignored files

While developing a Cordova app I realized I was checking in files that are generated at build time. For example, the www folder is copied into each platform (ios, android). I was not sure what I could safely ignore until I looked at ember-cli-cordova's gitignore.

So I changed my .gitignore to look something like:

...
# android
platforms/android/assets/www
platforms/android/bin
platforms/android/gen
platforms/android/local.properties
platforms/android/ant-build
platforms/android/ant-gen
platforms/android/CordovaLib/ant-build
platforms/android/CordovaLib/ant-gen
platforms/android/CordovaLib/bin
platforms/android/CordovaLib/gen
platforms/android/CordovaLib/local.properties
platforms/android/.gradle
platforms/android/res/xml/config.xml
...

But these folders are already checked in so now I have to git rm each of them.

Note: Don't forget to use the --cached or you will actually delete the files

$ git rm -r --cached platforms/android/assets/www
$ git rm -r --cached platforms/android/bin
... and so on

This is super tedious and I could easily miss a file or directory. There has to be a better way!

$ git rm -r --cached . && git add .

Git already knows how to ignore the files in .gitignore so just untrack all files in the project and add them back.

The staged changes should now be the new lines in .gitignore and the deleted files that accompany the changes.

The Alpha Commit

I like to read commit logs.

Today I wanted to see the first commit on a project. Here's what I used:

git rev-list --max-parents=0 HEAD

Translation:

Show me the commits that led to* HEAD *in reverse chronological order; then limit those results to the commits with no parent.

Here's a small modification, to show the entire commit rather than the SHA alone:

git show $(git rev-list --max-parents=0 HEAD)

Untrack A File Without Deleting It

Generally when I invoke git rm <filename>, I do so with the intention of removing a file from the project entirely. git-rm does exactly that, removing the file both from the index and from the working tree.

If you want to untrack a file (remove it from the index), but still have it available locally (in the working tree), then you are going to want to use the --cached flag.

$ git rm --cached <filename>

If you do this, you may also consider adding that file to the .gitignore file.

source

Search commit diffs

Ever had to search for specific changes when working with less than revealing commit messages? Search through the diffs with the -S flag on git log and it will give you back the commits that match that text in the diffs.

git log -S SomeClassThatGotRemoved

Clean Up Old Remote Tracking References

After working on a Git-versioned project for a while, you may find that there are a bunch of references to remote branches in your local repository. You know those branches definitely don't exist on the remote server and you've removed the local branches, but you still have references to them lying around. You can reconcile this discrepancy with one command:

$ git fetch origin --prune

This will prune all those non-existent remote tracking references which is sure to clean up your git log (git log --graph).

source

Git Log Since

At the end of each day, I try to record what I did, to jog my memory during the next morning's standup. This helps:

git log --since="24 hours ago"

I SSH into my work machine and run this in my project's root directory. Combined with an alias from the Hashrocket Dotmatrix, glg (git log --graph --oneline --decorate --color --all), I get a terse summary of the day's work, ready to be pasted into your note-taking or project management tool of choice:

$ glg --since="24 hours ago"
* 7191b92 (HEAD, origin/master, origin/HEAD, master) Good changes
* 3f4d61e More good changes
* ecd9dcd Even more
...

Drop the --all if you only care about your current branch (and --decorate --color, they don't add value when only one branch is being examined).

glg --since=midnight is also useful.

Caching Credentials

When public key authentication isn't an option, you may find yourself typing your password over and over when pushing to and pulling from a remote git repository. This can get tedious. You can get around it by configuring git to cache your credentials. Add the following lines to the .git/config file of the particular project.

[credential]
    helper = cache --timeout=300

This will tell git to cache your credentials for 5 minutes. Use a much larger number of seconds (e.g. 604800) to cache for longer.

Alternatively, you can execute the command from the command line like so:

$ git config credential.helper 'cache --timeout=300'

source

Grab A Single File From A Stash

In git, you can reference a commit SHA or branch to checkout differing versions of files.

$ git checkout d3d2e38 -- README.md

In the same way, you can snag the version of a file as it existed in a stash. Just reference the relevant stash.

$ git checkout stash@{1} -- README.md

source

Remove whitespace changes, then `git add -p`

git diff -w --no-color | git apply --cached --ignore-whitespace && git checkout -- . && git reset && git add -p

git diff -w --no-color creates a diff

git apply --cached --ignore-whitespace applies the diff ignoring whitepace, and indexes it

git checkout — . removes the unindexed “whitespace” changes

git reset resets the index to just he non-whitespace cahnges

git add -p adds the non-whitespace changes in patch mode

We did this with it alias gwap=“git diff -w --no-color | git apply --cached --ignore-whitespace && git checkout -- . && git reset && git add -p”

Poke around in your `git stash`

For some reason it's never occurred to me to see if there's more to git stash than just pop until now. Turns out you can git stash list to see a big list of all your stashes across all branches:

stash@{0}: WIP on email-unsubscribe: ce66d0f Add missing "EXISTS" to data view
stash@{1}: WIP on master: 484855f Add name to email 'from' address
stash@{2}: WIP on nav_settings: cd3cc88 User revokes pending invitation
stash@{3}: WIP on new_home_integration: d6345a3 Style payment form Stripe errors

You can then git stash apply some stash other than the top one, if you're into that, no matter what branch you're on. Just use the name, e.g. git stash apply stash@{2}.

Same goes for git stash show stash@{2}, plus you can add -v if you'd like the line-by-line view.

[Addendum: you can also use git show stash@{2}, which is equivalent to git stash show stash@{2} -v. h/t @joshbranchaud]

Determine The Hash Id For A Blob

Git's hash-object command can be used to determine what hash id will be used by git when creating a blob in its internal file system.

$ echo 'Hello, world!' > hola
$ git hash-object hola
af5626b4a114abcb82d63db7c8082c3c4756e51b

When a commit happens, git will generate this digest (hash id) based on the contents of the file. The name and location of the file don't matter, just the contents. This is the magic of git. Anytime git needs to store a blob, it can quickly match against the hash id in order to avoid storing duplicate blobs.

Try it on your own machine, you don't even need to initialize a git repository to use git hash-object.

source

Rebase all commits to whats been pushed

When git rebasing interatively, git rebase -i you need to specify a commit to rebase interactively back to, like:

git rebase -i HEAD^^

And generally I specify the commit to rebase to with as many carrots after HEAD as commits I needed. But generally you are rebasing commits that aren't yet pushed to master, so in that case just rebase back to 'origin/master'.

git rebase -i origin/master

Which Branches Have This Commit?

Imagine you have found an important commit in a complex git tree. Now you'd like to know which branches have this commit. Are the commit's changes on the branch we just deployed to staging?

--contains to the rescue:

$ git branch --contains <SHA>
* master
* staging
* feature/killer-feature

These three branches contain commit <SHA>.

View History of a File

Today we explored the history of a file with this command:

git log --follow -p -- file.txt

This displays every snapshotted change to 'file.txt' in your git history, including before and after file renames, in the highly readable --patch format. Learn from the past!

Source

Excluding Files Locally

Excluding (read: ignoring) files that should not be tracked is generally done by listing such files in a tracked .gitignore file. Though it doesn't make sense to list all kinds of excluded files here. For files that you'd like to exclude that are temporary or are specific to your local environment, there is another option. These files can be added to the .git/info/exclude file as a way of ignoring them locally.

Add specific files or patterns as needed to that file and then save it. Relevant files will no longer show up as untracked files when you git status.

h/t Dillon Hafer

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.

source

Push specific commit to remote repo

Sometimes you have multiple local commits and you want to push just part of them to the remote repo. Just specify the SHA1 for the revision you want and everything below it will be pushed.

git push <remote> <SHA1>:<branch>

Last Commit A File Appeared In

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.

Clone local git repo

I git clone github repositories all the time but I didn't realize you could clone a git repo on the file system.

git clone myproject myproject2

And what's crazier, I can clone the .git dir directly and I get the same result.

git clone myproject/.git myproject2

This is important because Capistrano creates a 'repo' dir on the target server that is a copy of the .git directory but is detached from any working tree (the set of files you'd normally see in a git repo).

To clone from a capistrano server you can copy the repo dir locally and git clone it.

Clear your Git Stash

While looking at my git tree today, I noticed several stale git stashes hanging around. This command made them all go away:

git stash clear

Renaming Git Branches

Git branches can evolve into roles they weren't meant to fill. An example is an application with a branch called 'good-master', where all current development is happening, despite the existence of a stale branch called 'master'. We can fix this!

One solution is to merge 'master' into 'good-master'. But that can be tricky if you don't want a lot of the changes.

You can also rename the branches. In our case, 'good-master' is the true master branch, so 'master' should be deleted (or preserved, if you prefer).

$ git branch -m master master-old # rename master to master-old
$ git push origin :master # delete master at origin
$ git push origin master-old # create master-old at origin (or delete it)

$ git checkout good-master
$ git branch -m good-master master # rename good-master to master
$ git push origin :good-master # delete good-master
$ git push origin master # create new master

Next, change your default Github branch to the new master. Anyone new the project will find a more logical repo to work with. Existing developers can reset their local repo with:

$ git fetch origin
$ git reset --hard origin/master

Rebase with Execution

A powerful flag available during a git rebase is -x, or exec. It allows you to run a command, or multiple commands, on each commit in the rebase.

At Hashrocket we have an alias called reset-authors that sets your last commit's authors to the state of your current git config. Sometimes I need to do this on more than one commit in the past, and this is where -x shines:

git rebase -i HEAD^^^ -x git reset-authors

The opens a rebase TODO list with the following details:

pick c246420 Fix README
exec reset-authors
pick cc32abc Clarify routes
exec reset-authors
pick 27b6c62 Convert HAML to ERB
exec reset-authors

Save and close, and reset-authors will run on each commit in the list.

Rebase to Root Commit

You can rebase all the way back to your root (initial) commit with:

git rebase -i --root

Attempting this without the --root flag will raise this error:

fatal: Needed a single revision
invalid upstream

Stashing Only Unstaged Changes

If you have both staged and unstaged changes in your project, you can perform a stash on just the unstaged ones by using the -k flag. The staged changes will be left intact ready for a commit.

$ git status
On branch master
...
Changes to be committed:

    modified:   README.md

Changes not staged for commit:

    modified:   app/models/user.rb

$ git stash -k
Saved working directory and index state ...

$ git status
On branch master
...
Changes to be committed:

    modified:   README.md

h/t Chris Erin

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

Git snapshot

To save a snapshot of your current work in git, try this command:

git stash save "snapshot: $(date)" && git stash apply "stash@{0}"

This saves your current work in a timestamped stash, without removing it. In Hashrocket's dotmatrix this command is aliased to git snapshot.

Undo a git mistake

git reflog is a record of your commands in Git. With it, you can undo almost mistake you make with Git.

$ git reflog

4bd0090 HEAD@{0}: <bad change>
46bd839 HEAD@{1}: <bad change>
967e214 HEAD@{2}: <last good change>
46bd839 HEAD@{3}: <good change>
967e214 HEAD@{4}: <good change>

Now:

$ git reset --hard HEAD@{2}

Or:

$ git reset --hard 46bd839