You can use
git-diff to see specific differences between two very long lines.
git diff --word-diff <file>
You can use
git-diff to see specific differences between two very long lines.
git diff --word-diff <file>
When you run Git commands that produce a bunch of output, Git will use a
pager to present the content starting at the beginning, rather than spitting
it all out to the screen at once. This will almost always come in handy for
By default, Git uses
less as its pager.
You can also choose to configure it to something else by updating your git config file or by running the following command:
$ git config --global core.pager 'more'
If you’d like to turn the pager off altogether, set it to a blank string.
$ git config --global core.pager ''
Use case: you’ve made a bunch of changes to a file, and want to reset certain lines of the file back to the
HEAD commit, while keeping other changes.
To solve this with git-fugitive, view the difference between your working copy and
:Gdiff . This will open two panes,
HEAD (left) and your working copy (right).
Find the lines you want to reset in the working copy. Highlight those lines in Visual mode, and enter
:diffget. Your working copy will restore the difference, for just the highlighted lines.
git bisect command is a
powerful tool for tracking down a past commit where something verifiable
about the code changed — whether it be visual or functional. After using
git bisect to traverse back and forth through your commit history, you may
be wondering where things stand.
command will show you each commit that has been inspected and whether you’ve
marked it as good or bad.
These records can be handy for double checking your work if you’re worried that you made a mistake along the way.
The standard way to list your branches is with the
git branch command. If
you use branches extensively for feature work and bug fixes, you may find
yourself overwhelmed by the list of branches trying to visually parse
through them for the one that you had worked on recently.
git for-each-ref command, we can produce a better list of
$ git for-each-ref --sort=-committerdate --count=10 --format='%(refname:short)' refs/heads/
The command itself will iterate over all of the repository’s refs and print
them out as a list. The
--sort=-committerdate option will ensure that list
is sorted by refs mostly recently committed to. The
limits the list output to 10 refs. The
format flag cleans up the output a
bit, only showing the shortname of the ref. Lastly, the
argument ensures that only local refs are included in the output, thus
ignoring remote refs.
The result is a list of local branches ordered by recency which generally corresponds to relevance.
man git-for-each-ref for more details.
I recently upgraded my Mac to High Sierra and got this gross message when I tried to use Git:
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
My machine was expecting Git to be implemented as an Xcode command line tool. To fix the problem, I simply installed Xcode.
git log command is going to give you a concise set of information
for each commit. Usually it is enough info. When it’s not,
git log can
provide additional information with the right flags. To include overall and
per-file stats on the number of insertions and deletions, use the
man git-log for more details.
If you ever need to search your commits for a pattern, you can use the following command to return the logs containing that pattern.
git log --grep pattern
-p flag with the
git log command to include the diffs along
with the rest of the information for each commit. Here is an example of
running this against my TIL repository.
$ git log -p commit b9809a329acd8150b2474168f8faaf008f376e35 Author: jbranchaud <email@example.com> Date: Wed Oct 11 07:27:53 2017 -0500 Add Inline Style Attributes Should Be Camel Cased as a react til diff --git a/README.md b/README.md index c982f8e..6ee7d32 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ smart people at [Hashrocket](http://hashrocket.com/). For a steady stream of TILs from a variety of rocketeers, checkout [til.hashrocket.com](https://til.hashrocket.com/). -_574 TILs and counting..._ +_575 TILs and counting..._ ---
man git-log for more details.
Today I learned that you can clone a Git repository to a directory with a different name than the repository name.
Here’s the command:
$ git clone https://github.com/cool_user/codez.git ~/codez-v2
My use case was cloning the same repo a bunch of times, making changes to the Git history in each, and not wanting to delete the directories as I went along.
Today I’m wrapping a PR with a large Git rebase. There are many commits on my branch that change the same file (
.circleci/config.yml) and aren’t valuable on their own. In the end, I want them all squashed into a single commit. The challenge is that I changed other files in between; it’s kind of a mess. How can I squash the branch down to something manageable?
One technique is to view just changes to my file:
$ git log --follow --oneline .circleci/config.yml c9f7108 Try to decode file before unzipping 87c8092 Quick push 327d419 Try this!
Using this list, I can execute a rebase that just squashes the commits that touch the file, leaving everything else intact.
As part of some automated scripting for an app deployment I wanted to be
able to get the short version of the latest commit to the current git repo.
rev-parse command is the perfect fit for this.
$ git rev-parse --short HEAD 708248b
man git-rev-parse for more details.
If you want to get a clean copy of a repository that you have locally, there is no need to go over the wire cloning it from the remote. You can clone from a local copy.
$ git clone my-repo/.git path/to/fresh-copy
man git-clone for more details.
Everyone knows that writing code is a competition in which she with the most commits wins. So, obviously, you want to periodically check the leaderboard to see who the 10x developer on the team is.
How do you do that?
This will give you a list of all the contributors to the repository and the number of commits they have made. You can add the
-s flag (for ‘summary’) to just see names and numbers.
Once you have acquired the evidence that you are indeed the mythical 10x developer, demand a raise from your employer and threaten to go work for Google if you don’t get it.
If you already work for Google, threaten to, I don’t know, get a job at SpaceX or something.
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 Arbona
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.
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 Karter
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.
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.
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 <firstname.lastname@example.org> Date: Sat Mar 28 12:52:19 2015 -0400 Add a basic README
Useful for explaining mystery files in the repository 🔎.
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 email@example.com:jbranchaud/pokemon.git (fetch) origin firstname.lastname@example.org: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 email@example.com:jbranchaud/pokemon_deluxe.git
If I check again, I can see it has been updated accordingly.
$ git remote -v origin firstname.lastname@example.org:jbranchaud/pokemon_deluxe.git (fetch) origin email@example.com: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.
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 <firstname.lastname@example.org> 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 <email@example.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
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.
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.
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.
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 firstname.lastname@example.org: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 Davey
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