In an earlier post I summarized a series of git commands to work with branches. In this post, I attempt to classify those commands, according to their purpose. My approach in this article is to take a series of common development scenarios using git and see how we can solve them. At the end of the article we will use what we have learnt (and some) to create a couple of useful git aliases.

Git branches are light and fast. You want to take advantage of these abilities by often forking and experimenting with new code, and sharing them with others when they’re ripe. Need to work on a breaking change without disrupting your main branch, no problem. Branch and push. Need to contribute to someone else’s branch, no problem. Pull, commit and push. Need to see if there’s a new branch to work on, no problem. Fetch and checkout.

These tasks are simple, but as with most things git they can be unintuitive depending on what you’re trying to do. When working with branches there are 3 important aspects that all developers will need to deal with :

Discoverability : You’re looking for a branch to contribute, how do you find it?

: You’re looking for a branch to contribute, how do you find it? Contribution : You want to contribute code, especially outside mainline. How do you pull in their changes? How do you create your own new branches?

: You want to contribute code, especially outside mainline. How do you pull in their changes? How do you create your own new branches? Clean up : How do clean up old stuff so others don’t push in old branches?

Before we move forward, let’s get some simple terminology out of the way :

Local Branch : A branch that is defined on your local machine. Remote Branch : A branch that is defined on a remote server. Remote Tracking Branch : A remote branch that you have linked to a local branch so can pull and push updates from the remote branch. HEAD : The HEAD is a pointer to the latest commit on a branch. Your local branche’s HEAD is always the last commit you made. A remotely-tracked branch’s HEAD is whatever the latest commit was when you last synced up. The actual remote branch may have yet another HEAD, for example when someone pushes new commits after you.

Listing branches, local and remotely tracked

Let’s cover a few basic commands here. These commands will show you what branch you’re working on, but with slightly different outputs.

git branch : This will list all the branches you have defined on your local machine, and will mark the current branch you’re on. Notice below that i’m on the master branch of my local git clone. workspace/eclipse/Tiny-Redis ‹master› » git branch adil gh-pages * master git branch --all : This command lists all local and remote-tracking branches. A remote-tracking branch is a remote branch you have linked to a local branch so you can pull and push. This will also show remotes for which the local branch has been deleted. This command will NOT show branches that have not been tracked, like any new branches on ‘origin’ (that’s later). workspace/eclipse/Tiny-Redis ‹master› » git branch -a adil gh-pages * master remotes/origin/adil remotes/origin/gh-pages remotes/origin/master remotes/origin/v1 git branch -vv : This is the same as git branch , but it shows local branches alongside their tracked remotes, and what the current HEAD is on each. This is a slightly more useful command when used during active development. workspace/eclipse/Tiny-Redis ‹master› » git branch -vv adil 7cd590a [origin/adil] Add dub.json gh-pages 3108052 [origin/gh-pages] v2.0.2 * master ab2272a [origin/master] v2.0.2

Discovering new branches

Here we’ll see a couple of ways to discover what branches are available on the remote server.

git ls-remote --heads origin : List all branches on ‘origin‘ server (origin is usually the default server you’ve cloned from) and their respective HEADs. This includes branches that are not being tracked on your system. Use this to find a new remote branch. git ls-remote --heads origin 7cd590a5dbb04594c93fee59e94bd2025e88ce78 refs/heads/adil 310805208339955df7805f5e043724c7c961edde refs/heads/gh-pages ab2272a22856230a4d56ca5c541e4adfe958e873 refs/heads/master Note that the origin argument above can be substituted with any git URL you have access to (or even a public URL), ex : workspace/eclipse/Tiny-Redis ‹master› » git ls-remote --heads http://www.kernel.org/pub/scm/git/git.git 9b7cbb315923e61bb0c4297c701089f30e116750 refs/heads/maint 3f1509809e728b70ea7912e4e1b40f22965e45ee refs/heads/master 41e5f3a738fb5958e2058f13714ffff159b26ff0 refs/heads/next 2707ec8700faa183c55f106d5dcd387e48dcaba6 refs/heads/pu 92d6a7876afe47c632fc9e7d1b16a8f2f40fbbaa refs/heads/todo git remote show origin : Shows local branches, remote branches, which branches are being tracked and to what. This is a good command to get a summary of your git repo. The output is very human-friendly. It even shows you old branches you should have pruned! (how’s that for house keeping) workspace/eclipse/Tiny-Redis ‹master› » git remote show origin * remote origin Fetch URL: git@github.com:adilbaig/Tiny-Redis.git Push URL: git@github.com:adilbaig/Tiny-Redis.git HEAD branch: master Remote branches: adil tracked gh-pages tracked master tracked refs/remotes/origin/v1 stale (use 'git remote prune' to remove) Local branches configured for 'git pull': adil merges with remote adil gh-pages merges with remote gh-pages master merges with remote master Local refs configured for 'git push': adil pushes to adil (up to date) gh-pages pushes to gh-pages (up to date) master pushes to master (up to date)

However, this command does NOT show local branches that are not tracking remotely. Use git branch -vv for that.

Working on a brand new branch

So you’ve decided to create a new branch and share the good stuff. Here goes.

git branch newfeature : Creates a new local branch called newfeature, that is untracked until you ‘ git push ‘. Note : Running this command will NOT switch your branch (see below). And, it does not output anything if successful. git checkout newfeature : Switch to your local newfeature branch. Errors out if the branch doesn’t exist. workspace/eclipse/Tiny-Redis ‹master› » git checkout newfeature Switched to branch 'newfeature' git checkout -b newfeature : This combines the previous 2 steps. Create a new local branch and switch to it. After you’ve made a commit you can create a new branch on remote using the next command. git push -u origin newfeature : This command pushes your commits to a new remote branch. The -u sets up a new remote branch and tracks the local branch to it. This command only needs to be run once. Subsequent pushes do not need the -u

workspace/eclipse/Tiny-Redis ‹newfeature› » git push -u origin newfeature Total 0 (delta 0), reused 0 (delta 0) To git@github.com:adilbaig/Tiny-Redis.git * [new branch] newfeature -> newfeature Branch newfeature set up to track remote branch newfeature from origin.

Contributing to an existing remote branch

When you want to contribute code to an existing branch, there are three steps :

Finding new branches : For that see “Discovering new branches” above. Syncing : New branches can be created anytime, and your system may not be synced with the remote server. git fetch –all will sync your local repo with the remote origin. Comitting back : This is as simple as checking out the branch (after a git fetch) and pushing upstream.

So let’s get on with it

git fetch --all : This command fetches all remote branches and tags. It’s worth doing this every now and then, just to see what features are in development. You’ll need to do this before you can start contributing to a remote branch. Use the -n option if you don’t want to fetch tags workspace/eclipse/Tiny-Redis ‹newfeature› » git fetch --all Fetching origin git checkout --track origin/newfeature : Create a new local branch called ‘newfeature‘ that tracks to an existing remote branch ‘origin/newfeature‘. Do this when you want to contribute to an existing remote branch. To use this command you must first run git fetch origin newfeature or git fetch --all , so you can track the remote branch on your system.

Deleting branches

When the time comes to finally clean up old code, here’s a few commands to delete local and remote branches

git branch -D newfeature : This command deletes the newfeature local branch workspace/eclipse/Tiny-Redis ‹master› » git branch -D newfeature Deleted branch newfeature (was ab2272a). git push origin :newfeature : The ‘:’ prefix tells git to delete newfeature on the remote server workspace/eclipse/Tiny-Redis ‹master› » git push origin :newfeature To git@github.com:adilbaig/Tiny-Redis.git - [deleted] newfeature git remote prune origin : Finally, this command prunes old branches on the remote server that are not being tracked by anyone. workspace/eclipse/Tiny-Redis ‹master› » git remote prune origin Pruning origin URL: git@github.com:adilbaig/Tiny-Redis.git * [pruned] origin/v1

Simplify with Git Aliases

Now that we know all of the above, let’s use what we’ve learnt to create git aliases. Aliases are user-defined git commands that can combine multiple commands into one. I won’t get into the details here, you can read them up on Git SCM. In short, they can simplify our lives by creating more intelligent, user-friendly commands.

git delete-branch <branch name>

Our first alias : delete-branch . It takes in the name of the branch and deletes both local and remote versions. Before we begin, here’s the code to create the alias :

git config --global alias.delete-branch '!_() { test -n "$1" && git branch -D $1 && git push origin :$1; }; _ '

Let’s break that down so we can understand it in parts. git config alias.delete-branch '..' creates the alias. --global makes this alias available to all repos on our system, even ones we haven’t created yet. You usually want to define aliases in a repo agnostic way and make them available globally. They’re simpler to understand that way and more generally useful.

The alias definition is as follows :

!_() { test -n "$1" && git branch -D $1 && git push origin :$1; }; _

The ‘ ! ‘ tells git to run the rest of the string in bash (or your default shell). ‘ _() { .. }; _ ‘ : This part wraps the string in a bash function and calls it. Git appends user provided parameters to the end of the alias. This provides a way to pass params to your alias, but it can cause your alias to behave strangely when it has a complicated setup. By wrapping it in a function we don’t need to worry about coming across strange append errors, the params are passed to the function and we don’t need to deal with them unless we want to.

‘ test -n "$1" ‘ makes sure you don’t run anything if you pass in an empty string. $1 is the first parameter you pass into this command. Finally ‘ git branch -D $1 && git push origin :$1 ‘ deletes the local branch and it’s remote branch. In case, a local branch doesn’t exist (like when you make a typo) the command will error out. Let’s try this :

workspace/eclipse/Tiny-Redis ‹master› » git branch newfeature; git push -u origin newfeature Total 0 (delta 0), reused 0 (delta 0) To git@github.com:adilbaig/Tiny-Redis.git * [new branch] newfeature -> newfeature Branch newfeature set up to track remote branch newfeature from origin. workspace/eclipse/Tiny-Redis ‹master› » git config --global alias.delete-branch '!_() { test -n "$1" && git branch -D $1 && git push origin :$1; }; _ ' workspace/eclipse/Tiny-Redis ‹master› » git delete-branch newfeature Deleted branch newfeature (was ab2272a). To git@github.com:adilbaig/Tiny-Redis.git - [deleted] newfeature

git new-branch <branch name>

This alias will take a branch name and create a local branch that is remotely tracked. It will also do some sanity checks. If a remote with the same name exists, this alias will pull and track it locally. And in the case the branch already exists locally, it’ll print a message and error out.

When you create a new branch you want to be sure a branch of the same name does not exist remotely. Technically you can track a local branch to a remote branch with a different name, but it’s usually unintuitive and not recommended.

So here’s the script :

git config --global alias.new-branch '!_() { local LOCAL_BRANCH_EXISTS=$(git branch | grep -sx $1 > /dev/null; echo $?); if [ "$LOCAL_BRANCH_EXISTS" -eq 0 ]; then echo "Branch \"$1\" already exists!" exit 0; fi local REMOTE_BRANCH_EXISTS=$(git ls-remote --heads | grep -x "$1" > /dev/null; echo $?); if [ "$REMOTE_BRANCH_EXISTS" -eq 0 ]; then echo "git fetch --all; git checkout -b $1 --track origin/$1; git pull origin $1;"; git fetch --all; git checkout -b $1 --track origin/$1; git pull origin $1; else echo "git checkout -b $1; git push origin $1;" git checkout -b $1; git push origin $1; fi }; _ '

This script looks long but is essentially simple. Let’s break it down into logical chunks.

local LOCAL_BRANCH_EXISTS=$(git branch | grep -sx $1 > /dev/null; echo $?); if [ "$LOCAL_BRANCH_EXISTS" -eq 0 ]; then echo "Branch '$1' already exists!" exit 1; fi

This code checks to see if the branch exists. We just grep the output of git branch for this. If we find a local branch, we print a message and error out.

local REMOTE_BRANCH_EXISTS=$(git ls-remote --heads | grep -x "$1" > /dev/null; echo $?); if [ "$REMOTE_BRANCH_EXISTS" -eq 0 ]; then echo "git fetch origin $1; git checkout -b $1 --track origin/$1; git merge origin/$1;"; git fetch origin $1; git checkout -b $1 --track origin/$1; git merge origin/$1;

Here we check if a remote branch exists, by grepping the git ls-remote --heads output. If it does, we fetch that branch and create a local branch tracked to the remote, with ‘ git checkout -b $1 --track origin/$1;' . ‘ git merge origin/$1' merges the remote commits to the local one, so you can begin from where it left off.

And finally,

else echo "git checkout -b $1; git push origin $1;" git checkout -b $1; git push origin $1; fi

We create a new branch and push it online if none of the above is true.

There you have it, two useful git aliases and an understanding of how they work. Leave your comments below.