git add --patch gives you the opportunity to make a decision on every code change in all files individually, providing you a menu that looks like this:
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?
d - do not stage this hunk or any of the later hunks in the file
This comes in handy when there is a host of whitespace changes in a file that you don’t want to commit. Just hit
d to skip all those whitespace changes and go to the next file.
Interactive rebasing is a powerful way to manage and tend to the history of a git repository. Rewording and squashing commits are fairly common actions. But what if you need to run some arbitrary command against a series of recent commits?
This is where the
--exec flag comes in to play.
$ git rebase -i HEAD~3 --exec "git commit --amend --reset-authors -CHEAD"
This generates an interactive rebase file that you can review and save when ready.
As you can see, the specified command is prepared for execution for each commit involved in the rebase.
h/t Patricia ArbonaTweet
Today I learned a new Git trick: to show branches already merged into a branch, try this command:
$ git branch --merged master add-basic-auth add-channels add-developer-username add-developers add-developers-show add-hashtag-to-slack-post
For an open source project like Today I Learned, this is an interesting way to get a sense of the project.Tweet
Today my pair did something I’d never seen: create an empty commit. Out goal was to test a third-party integration, but we didn’t have a meaningful change to contribute at the time. In this situation, I think an empty commit is a good compromise, because it’s honest about our intent— to simply kick off a remote process.
Here was the command:
$ git commit --allow-empty
h/t Dorian KarterTweet
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
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
destination, for example, we can issue the
$ git remote rename origin destination
man git-remote for more details.
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.
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
$ 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 command allows you to specify a range of commits to be
cherry picked onto the current branch. This can be done with the
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
HEAD. This is because the
lower-bound is exclusive. If you’d like to include
B as well. Try the
$ git cherry-pick B^..D
man git-cherry-pick for more details.
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 <email@example.com> Date: Sat Mar 28 12:52:19 2015 -0400 Add a basic README
Useful for explaining mystery files in the repository 🔎.Tweet
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
$ git remote -v origin firstname.lastname@example.org:jbranchaud/pokemon.git (fetch) origin email@example.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 firstname.lastname@example.org:jbranchaud/pokemon_deluxe.git
If I check again, I can see it has been updated accordingly.
$ git remote -v origin email@example.com:jbranchaud/pokemon_deluxe.git (fetch) origin firstname.lastname@example.org:jbranchaud/pokemon_deluxe.git (push)
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
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
git show HEAD..master --reverse
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:
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
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.Tweet
Git has a built-in
grep command that works essentially the same as the
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
man git-grep for more details.
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
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.
man git-show for more details.
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:
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
If you want to know what has changed at each commit in your Git history,
then just ask
$ git whatchanged commit ddc929c03f5d629af6e725b690f1a4d2804bc2e5 Author: jbranchaud <email@example.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 <firstname.lastname@example.org> 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
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.
man git-whatchanged for more details.
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:
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
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
git clean -fd
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.
git diff --help for more info.
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.
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
master, excluding changes in the
spec directory. From here you can check out the files in either state and explore.
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 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 (
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.Tweet
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.
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
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.
man git-merge-base for more details.
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
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
> 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.Tweet
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
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 email@example.com:til
we can simply:
git remote add production til-production:til
It’s a little bit less to think about.
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.
So if you want to see more (or less) context when running a git diff command you can use
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
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
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
[diff] algorithm = patience
or it can be set from the command line with:
$ git config --global diff.algorithm patience
h/t Josh DaveyTweet
Today I learned that I can combine
-S 'my-search' and
git log command. Here is an example:
git log -p -S 'create_or' app/models/*.rb
-p=> equivalent to
git difffor each commit found on log
-S 'my-seach'=> filtering by commits that add/remove this content
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
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
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
Once installed, get more info here:
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.
$ 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.
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
echo "This happens before the commit"
For every project you want to use these templates restart git project running:
These will create symbol links like:
lrwxr-xr-x pre-commit -> /Users/vnegrisolo/.git-templates/hooks/pre-commit
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
git clone --depth 1 https://github.com/dkarter/bullets.vim
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.Tweet
-- <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.
--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.
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 firstname.lastname@example.org: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 email@example.com:hashrocket/hr-til - [deleted] v16.0.0
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 --firstname.lastname@example.org * 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 ...
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.Tweet
--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 <email@example.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
This program reads the output of diff and displays a histogram of the insertions, deletions, and modifications per-file.
man git-show and
man diffstat for more details.
Today I learned how to checkout and inspect a Git pull request locally.
.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.
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
$ git rm --cached -r <directory>