Git Tips From the Pros

Git Tips From the Pros

Tutorial Details
  • Difficulty: Advanced
  • Completion Time: 20 Minutes

You're already using source control for managing your code, right? You might even be using your SCM as the central piece of your workflow, like we do at New Relic.

In this article, we're not going to review the basics of source control management, regardless of which one you use. Let's just assume that you already know how to get around. What we are going to cover is how the pros use git. We'll take a look at some of the advanced features and workflows that you might not already be familiar with. Hopefully, you’ll walk away with your mouth agape at the sheer possibilities that git provides!

If you are anything like me, you love to explore how other developers work.

For the uninitiated, or those coming from another SCM, Git is a distributed version control system. It is free and open source, has a tiny footprint, and can fit within the workflow that suits you best. Generally, it doesn't force you to work in a particular way, which means there are many different methodologies on how to use its features, like staging areas, branching and tagging releases. If you are anything like me, you love to explore how other developers work. So get ready to start modifying your .gitconfig, because you're in for a treat. Let’s see how the pros use git.


Stage Your Changes in Hunks

You are likely familiar with accidentally modifying a single file for two different reasons without committing in between.

You are certainly familiar with adding files to the staging area with the appropriately named add command. And you are likely familiar with accidentally modifying a single file for two different reasons without committing in between. So you will sure have a git log filled with messages like "Edit X and change unrelated Y". If this sounds like your workflow, then interactive adding is your new best friend.

Interactive adding, or adding a patch, walks you through your changes one hunk at a time. When you add a file with the -p command, you will be prompted at each logical change (i.e., successively edited lines will be grouped together). There are a number of choices you can make on each hunk, from splitting the current hunk into smaller ones, skipping a hunk, or even manually editing it. Use the ? option to see a complete list of commands.

Getting started with staging hunks is as simple as:

git add -p <FILE>

Checkout Your Last Branch

As a good coding citizen, when you come across something that needs a quick fix or cleanup, you should probably take a moment to change it. But if you are using a heavy feature-branch workflow, then you don't want that unrelated fix in your feature branch. This means you'll need to stash your current changes, change to your master branch and then make the fix there. Bouncing around between branches can be tedious, but, luckily, there is a quick shortcut for switching to your last branch. (via Zach Holman)

git checkout -

This syntax should look pretty familiar to *NIX users. The cd command has a similar shortcut (cd -) that will jump to the last directory you were in. You’ll never have to remember what you named that feature branch when you need to switch back; just git checkout -.


Show Which Branches are Merged (or not)

When working with feature branches, you can quickly create so many that clutter up the output of git branch --list. Every now and again you want to get rid of the branches that have made it into master. But you probably have a quick pause before you git branch -d <BRANCH>, but with the below commands you can confidently delete them without a second thought. (via Zach Holman)

If you want to see which local branches you have that are merged into the branch you are currently on, then all you need is:

git branch --merged

The reverse is also available. Show which branches haven't been merged into the currently selected branch with:

git branch --no-merged

Mash this up with a couple easy UNIX tools and you can quickly delete everything that has already been merged:

git branch --merged | xargs git branch -d

Grab a File from Another Branch without Switching Branches

Let's say that you're experimenting with some refactorings, and you have a few branches that have various changes you've made. If you have changes in a file in some distant branch that you want to bring into your current working branch, then you could do any number of steps. Without the below tip, you'd probably stash your current changes, switch branches and grab the file contents you want to change, switch back (with git checkout - of course) and make your edits. Or you could simply checkout just that file which will merge it into your current branch (via Zach Holman):

git checkout <BRANCH> -- path/to/file.rb

Git Branches Sorted by Last Commit

So you've got the cluttered branch list that we talked about before; some of those you've cleaned up with the --merged flag. But what about all those other branches? How do you know which ones are useful or entirely out of date? The for-each-ref command will output a list for each branch and show the reference information for the last commit. We can customize the output to include some useful information, but, more importantly, we can sort the list by date. This command will give us a list of branches with the last commit message and committer, sorted in descending date order. (via Rein Henrichs)

git for-each-ref --sort=-committerdate --format='%(committerdate:short) %(refname:short) [%(committername)]'

While you could type this command each time, I highly recommend making it an alias and save yourself some serious headaches.

git config --global alias.latest "for-each-ref --sort=-committerdate --format='%(committerdate:short) %(refname:short) [%(committername)]'"

People in Glass Houses Shouldn't Use Git Blame

Or at least they shouldn't use git blame without one of the options flags below. Git blame is powerful; it's basically like using science to prove you're right. But be careful, many changes are superficial and to find the real source of the in-question code takes a bit more hunting. Things like removing white space, moving text to new lines, or even moving text from another file can be ignored to get to the original author of the code much easier.

Before you git blame someone, make sure you check one of these:

git blame -w  # ignores white space
git blame -M  # ignores moving text
git blame -C  # ignores moving text into other files

Find a String in the Entire Git History (and Remove It)

From time to time, you need to hunt down a line of code you know you wrote but just can't find. It could be stuck in some distant branch, deleted a long long time ago, or hiding in plain site; but either way you can find any string in your entire git history by mashing up a few commands. First, we're going to get a list of all commits, and then grep each of them for our string.

git rev-list --all | xargs git grep -F '<YOUR STRING>'

You probably have a friend who has accidentally committed sensitive data to a repo: access keys, passwords, your grandmother's secret marinara recipe. The first thing they should do is change their passwords and revoke access with those keys (and apologize to your grandmother). Next, you'll want to hunt down the offending file and remove it from the entire git history, which sounds far easier than it actually is. After this process is complete, anyone that pulls in the cleaned changes will have the sensitive data removed as well. Forks of your repo that do not merge your upstream changes will still contain the compromised files (so don't skip changing passwords and revoking access keys).

First, we'll rewrite the git history for each branch, removing the file with the sensitive data.

git filter-branch --index-filter 'git rm --cached --ignore-unmatch <FILENAME>' --prune-empty --tag-name-filter cat -- --all

Add the file to .gitignore and commit to update .gitignore.

echo <FILENAME> >> .gitignore
git add .gitignore
git commit -m "Add sensitive <FILENAME> file to gitignore"

Since we are rewriting history, you'll need to force push the changes to your remote.

git push origin master --force

The compromised files still exist in your local repo, so you'll need to do a few clean-up tasks to purge them entirely.

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

Your friend's repo should be free of sensitive data and you'll be the hero for helping them with your pro git knowledge. (via StackOverflow and GitHub)


Ignore Changes in a Tracked File

Working with someone else's code in your environment can mean that you need to make any number of config changes to get the application running. It is all too easy to accidentally commit a change to those configs that were meant exclusively for your environment. So, instead of always watching out for those files and having them linger in the "modified" staging area, you can simply tell the git index to ignore changes to that file. You can think of this somewhat like a git ignored file that stays with the repo. (via Arnaud Coomans)

git update-index --assume-unchanged <FILENAME>

Zero Out a Branch's History

Sometimes starting from scratch is exactly what you need to do, for any number of reasons. Maybe you've inherited a codebase that you can't ensure is safe to open source, maybe you're just going to try something entirely new, or maybe you're adding a branch that serves a separate purpose that you want maintained with the repo (like GitHub Pages). For this case, there is a very simple way to create a new branch in your repo that essentially has no history. (via Nicola Paolucci)

git checkout --orphan <NEWBRANCH>

Aliases You Can't Live Without

Stop wasting time typing long commands and make yourself a few useful aliases.

No discussion of git would be complete without talking about various aliases that will literally save you minutes a year in saved keystrokes. Stop wasting time typing long commands and make yourself a few useful aliases. Aliases can be made by adding them to your .gitconfig file or using the command-line git config --global alias.<NAME> "<COMMAND>". Below are just a sample of alias that you can use as a springboard for ideas.

co: with a feature branch workflow, you'll be moving between branches regularly. Save yourself six characters every time.

co = checkout

ds: it is always best practice to review the changes you're going to commit before making the actual commit. This allows you to catch typos, accidental inclusion of sensitive data and grouping code into logical groups. Stage your changes and then use git ds to see the diff of those changes.

ds = diff --staged

st: you should be pretty familiar with the verbose output of git status. At some point you'll want to skip all the formality and get down to business. This alias shows the short form of status and includes the branch details.

st = status -sb

amend: did you forget to include a file with your last commit, or maybe you had one tweak you needed to make? Amend the staged changes to your last commit.

amend = commit --amend -C HEAD

undo: sometimes, amending your last commit isn't enough and you'll need to undo it instead. This alias will step back one commit and leave the changes from that commit staged. Now you can make additional changes, or recommit with a new message.

undo = reset --soft HEAD^

ls: working on a codebase with a group of developers means trying to keep up with what people are working on. This alias will provide a one line git log including date and committer name.

ls = log --pretty=format:"%C(yellow)%h %C(blue)%ad%C(red)%d %C(reset)%s%C(green) [%cn]" --decorate --date=short

standup: this alias is great for reviewing what you worked on yesterday for any type of daily standup, or just to refresh your memory in the morning.

standup = log --since '1 day ago' --oneline --author <YOUREMAIL>

graph: a complex git history can be difficult to review in a straight line. Using the graph flag shows you how and when commits were added to the current branch.

graph = log --graph --pretty=format':%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset'

In Closing

Git can be both amazingly simple and mind-blowingly complex. You can start with the basics and work yourself into more complex graph manipulation over time. There's no need to grok all of it before you can use it. The command that will be most powerful as you learn is man git-<command>-<name>. Try using it before you refer to Google for an answer.

You can learn more about how the company I work for you, New Relic, uses git on our blog, or give New Relic Pro a try for free. Thanks for reading! If you have any questions, let us know below!

Tags: git
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://twitter.com/zaneMATTHEW zane matthew

    Some good ones there, didn’t know about add -p option, I used to use git from the command line 100% of the time, but Tower is pretty awesome and handles “most” of what I’m doing these days…but command line does rock!

  • Tomasz Borychowski

    Thank you!

  • gregchapple

    Great article. If you are using zshell instead of bash you can install ohmyzsh which has a git plugin (among many others) that has some really handy aliases. Like: gst – git status; gco – got checkout; and so many more! Its really great, saved me lots of time.

    • shakil

      Right

  • Jon R.

    A few things. You can setup aliases in bash and even put them in dropbox to persist across your machines. Also, no mention of Git Flow, which should be the standard workflow for any project.

    • http://twitter.com/RussellUresti Russell Uresti

      While Git Flow is great, I would not say it should be the standard workflow for any project. Specifically, it’s not a good solution for any project that’s doing CI (continuous integration/continuous deployment). This is how Github, itself, operates: http://scottchacon.com/2011/08/31/github-flow.html

  • http://www.facebook.com/zselleristvan Istvan Zseller

    Fine explanation. Most of the tutorials about Git end with merging and pushing, I only remember one about reverting changes. Here we have all the techniques that needed to be incorporated in the everyday workflow, all in one place . Thanks a bunch!

  • Moses Adetola

    Really helpful

  • bert

    thanks

  • mattsah

    Anyone using git should watch this video multiple times:

    http://vimeo.com/49444883

    • Ian Simmons

      downloading in HD for multiple viewing pleasure :-)

  • http://www.clippingpathcenter.com/clipping-path-service.php mamun

    Amazing articles and its awesome..
    Thanks to share..

  • http://www.graphicexpertsonline.com/clippingpath.html mam_raj07

    Awesome and difference articles…

    it also helpful
    thanks to share…………….

  • Matt Thornton

    I like this better than the ls posted above: log –first-parent –graph –pretty=format:’%Cred%h%Creset | %cn | -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset’ –abbrev-commit –date=relative

    • Matt Thornton

      Also, love the standup alias. I was doing git log | grep -m10 “my name” before. I did have to put an equal on the –author to get it to work (–author=myemail@myhost.com).

  • אורי לוי

    Super cool! thanks

  • Arash M

    Very cool! Thanks :)

  • Ian

    Nice article: we have some more basic free Git tutorial videos at http:\ava.co.ukgit We also use Smartgit which is quite nice.

  • MarcioAlmada

    Best article I’ve seen in a LONG time here! Please cover git-flow next time :)

  • Adrian

    Something I often need to do is add stuff in my last commit It seems the amend shortcut you list does that, but here is the alias I use:

    fix = “!f() { git commit -a -m “fixup! $(git log -1 –pretty=format:%s)” && git rebase -i –autosquash HEAD~4; }; f”

    This and other git advices here: https://blog.mozilla.org/webdev/2011/11/21/git-using-topic-branches-and-interactive-rebasing-effectively/

  • http://cubicleninjas.com/ Tyler Etters

    git checkout -Awesome.

  • pavelnikolov

    I found few things I didn’t know. Thank you for your post.

  • http://s1n4.com/ Sina Samavati

    Useful tips, thanks

  • D.S.

    Very helpful tips. Thank you.

  • laravelbook

    Thanks! I’ve just learned some new tricks!

  • SairamKunala

    Alias of aliases which I found somewhere

    # git alias # prints out
    a => !git add -A . && git status
    alias => !git config –list | grep ‘alias.’ | sed ‘s/alias.([^=]*)=(.*)/1 => 2/’ | sort
    ….

  • http://kimkling.net/ Kim Kling

    Great, bookmarked!

  • http://twitter.com/xaviesteve Xavi Esteve

    You have opened my eyes and saved me hours of typing! Simply amazing, thank you!

    When I use the ‘ls’ alias I get:

    $ git ls
    fatal: ambiguous argument ‘%C(blue)%ad%C(red)%d’: unknown revision or path not in the working tree.
    Use ‘–’ to separate paths from revisions

    I’m probably doing something stupid myself. Is anyone else experiencing this or is there any specific scenario where it works?

    • Guest

      I am as well Xavi

    • http://twitter.com/ky Kyle Bradshaw

      The example is incorrect: they are using double quotes ” where it should be single ‘

      ls = log –pretty=format:’%C(yellow)%h %C(blue)%ad%C(red)%d %C(reset)%s%C(green) [%cn]‘ –decorate –date=short

      works

  • http://twitter.com/tommyjmarshall Tommy Marshall

    gca = git commit -am

  • diseño web cali

    Interesting!!

    diseño web cali

  • http://twitter.com/RussellUresti Russell Uresti

    Question regarding the one-liner to remove merged branches. When I do a git branch –merged from master, master itself shows up in the list. Would the line of code provided also delete my master? I haven’t tried it yet, because I’d rather not lose it (though I could just check it out again from the remote, I’m hesitant to do something so destructive).

  • Daniel

    A tool that needs so many hacks is a poor tool

  • anit.shrestha

    Awesome INSIGHTS. Thankx a lot.

  • Hauleth

    My favourite aliases are:

    todo grep –heading –break -I –ignore-case -e ‘[^a-z]TODO:’
    fix grep –heading –break -I –ignore-case -e ‘[^a-z]FIX:’ -e ‘[^a-z]FIXME:’
    ca commit –amend –reuse-message=HEAD
    lg log –color –graph –pretty=format:’%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)%Creset’ –abbrev-commit