Be Present Now

Sometimes witty, sometimes childish. Add gesticulation, shake.

Git: Twelve Curated Tips And Workflows From The Trenches

My Evernote tech-tips folder and my Notational Velocity stash have been collecting a huge list of git tips, many of which I have to look up again and again. Until I finally give up and create aliases for them (by the way you can find some of my more advanced git aliases explained in my previous post).

I expect many git practitioners will find them useful as much as I do. Here we go, let’s start from tiny and simple things first.

Making ‘git diff’ wrap long lines

This has been driving me mad for a while. My git diff would not wrap lines and leave a lot of information hidden from view in my terminal. Luckily the solution for this is easy.

If you use less as default pager just type -S while viewing the diff to reenable wrapping in less.

You can also use git config to setup pager to wrap:

$ git config core.pager 'less -r'

Sets the pager setting for the current project.

$ git config --global core.pager 'less -r'

Sets the pager globally for all projects. (Stack Overflow reference)

Set a global proxy

In some network configurations you might need to use a proxy with your git. It is a straight forward oneliner to do so:

git config --global https.proxy https://user:password@address:port

Clone only a specific branch

For big projects or for ease of access sometimes you want to clone just one branch. To clone a branch without fetching other branches here’s what you can do:

mkdir $BRANCH
cd $BRANCH
git init
git remote add -t $BRANCH -f origin $REMOTE_REPO
git checkout $BRANCH

(Stack Overflow reference)

Diff file against remote branch

This is basic git knowledge but it does not hurt to show you. Here is the outline:

git diff localbranch remotebranch filepath

As an example, supposing you have a few local and remote branches fetched and up-to-date:

[developer][ubuntu:~/delixl][631]
$ git branch -ra
* 631
  fsh-502
  master
  remotes/6.29
  remotes/6.30
  remotes/6.31
  remotes/ECM-1670.3
  remotes/trunk

You can perform a diff with one of the remote branches with:

git diff master ECM-1670.3 pom.xml

diff --git a/pom.xml b/pom.xml
index f3fc810..27154e3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
  <groupId>nl.delixl</groupId>
  <artifactId>delixl</artifactId>
  <packaging>pom</packaging>
-  <version>6.32-SNAPSHOT</version>
+  <version>7.30-SNAPSHOT</version>
[...]

List all deleted files in the repository

You might want to restore them, you might want to double check some merge behavior, in any case it’s quite useful to be able to list all the files that have been deleted in your repository. Here’s how to go about it:

git log --diff-filter=D --summary

If you want to restore some of them see this.

If you don’t want all the information about which commit they were removed in, you can just add a grep delete in there.

git log --diff-filter=D --summary | grep delete

(Stack Overflow reference)

Search for a string in all revisions of entire git history

I had to do this many times. I remember a piece of code, a function name, a constant, a file, that was there. And now it’s gone. What happened? In which commit was it removed? This trick rocks as you can search through the entire git diff history for a string.

Update: Jaime in the comments thread pointed me to the right way to do this:

git log -Stext

I was doing it in an inefficient way found on SO:

git rev-list --all | (
    while read revision; do
        git grep -F 'Your search string' $revision
    done
)

(Stack Overflow reference)

Apply a patch from another (unrelated) local repository

The proper way to cherry-pick a commit from another repository is to first add the other repository as a remote, fetch its changes and then cherry-pick the commit.

But if you want a raw and quick procedure, that works even between unrelated repositories you can do:

git --git-dir=../some_other_repo/.git format-patch -k -1 --stdout <commit SHA>| git am -3 -k

(Stack Overflow reference)

Making a more recent branch the new master

Sometimes you work on a separate better_branch for quite some time, and you’re so satisfied with it that it makes sense to just make it the new master. So how do you do it? As usual stack overflow to the rescue:

  1. Switch to the better_branch:

    git checkout better_branch
    
  2. Keep the full content of better_branch but record a merge:

    git merge --strategy=ours master
    
  3. Switch to master again:

    git checkout master
    
  4. Fast-forward master up to the merge:

    git merge better_branch
    

If you want your history to be a little clearer, you can customize the commit message to clarify what you are doing. You can change the second step to:

git merge --strategy=ours --no-commit master

git commit # Here add your custom message to the commit template

Adding an initial empty commit to a branch to allow full rebase

This tip rewrites history so as usual you should only do it on branches that you haven’t shared with anyone, otherwise you risk breaking things for all your peers.

  1. Create a new empty branch i.e. newroot

    git checkout --orphan newroot
    git rm --cached -r .
    git clean -f -d
    
  2. Create the empty commit

    git commit --allow-empty -m '[empty] initial commit'
    
  3. Replay the whole content of the branch

    git rebase --onto newroot --root master
    
  4. Delete the temporary branch newroot

    git branch -d newroot
    

Done. Your master had its history rewritten to include an empty root commit. (Stack overflow reference)

Zero a branch to do something radically different

Sometimes you want a branch where you can start everything from scratch, or keep some code that logically is related to your master but tracks another cross-functional aspect of it. Like gh-pages in a github project.

So how do you zero a branch, erasing all its history so that you can start something new?

Update: helpful user in the comments and in the reddit thread pointed me to a much easier way to accomplish this:

git checkout --orphan new_empty_branch

My original process was way more convoluted:

  1. Checkout a branch:

    git checkout -b branch_to_zero
    
  2. Follow the tip above about adding an initial empty commit and after that you can very easily zero a branch by doing a simple reset.

  3. Reset hard the branch to the initial commit you just created

    git reset --hard initial_commit
    

How to modify a specified commit?

The amend command (git commit --amend) is very useful to rework your last commit before pushing it. But what if the commit you want to change is not the last?

You can use git rebase, for example, if you want to modify commit bbc643cd, run:

$ git rebase bbc643cd^ --interactive

In the default editor, modify pick to edit in the line whose commit you want to modify. Make your changes and then stage them with:

$ git add <filepattern>

Now you can use:

$ git commit --amend

To modify the commit, and after that

$ git rebase --continue

To return back to the previous head commit. (Stack Overflow reference)

How to stash only one file out of multiple files that have changed

git stash --keep-index Will stash everything that you haven’t previously added. Just git add the things you want to keep, then run it.

By using this you can split an old commit into more than one changeset:

  1. Rebase interactively from your last good commit:

    git rebase -i last_good_commit
    
  2. Mark some changes as edit.

    git reset HEAD^
    
  3. Add the files you want to keep in this change:

    git add file1 file2 file3
    
  4. Stash everything that you haven’t previously added:

    git stash --keep-index
    
  5. Fix things up as necessary. Don’t forget to git add any changes.

    git commit
    
    git stash pop
    
  6. Repeat, from step 2, as necessary.

    git rebase --continue
    

(Stack Overflow reference)

Conclusion

I hope you enjoyed these tips and workflows roundup. More is in store if there is interest.


Notifications for new articles and virtual interactions with the author of this (me!) are available on twitter @durdn.

5 December 2012

Sign up to receive my next blog posts via email: