There are many different GUIs for Git these days: GitHub for Mac, Tower, SourceTree, Xcode and more. While these options are fine for most situations, many experienced developers would tell you that the Command Line Interface (CLI) is the best option and should be the primary tool you use.
While “best” is an imprecise, subjective term, the Git CLI is certainly the most powerful and versatile way to use Git, as the GUI options usually just run the CLI commands under the hood. Anything a Git GUI can do, the command line can do — probably better.
This Git tutorial introduces you to a few ways to use the command line more effectively with Git.
In particular, you’ll learn how to make the command line a friendlier place by adding autocompletion, and how to make the command line prompt provide you with help and Git guidance.
Note: It’s often been the case that graphical Git clients have simply executed Git commands under-the-hood. However, an open source, portable, pure C implementation of the Git core methods is now available in libgit2, and is being used by industry heavyweights, such as Microsoft, to power their Git tools
If you’re new to Git or just need to brush up, here are two links to help you get up to speed.
Also, before continuing, make sure that you’ve installed Xcode’s Command Line Tools (CLT). To find out what you have, open a terminal window and at the prompt enter
If you see no error message, you’re OK.
If the tools are not there, type the line below into Terminal, and then follow the instructions in the resulting dialog.
Why You Care About Fancy Git Commands
…but might not realize it yet.
To start, take a quick look at some of the most common issues people have with Git repositories, or repos for short. Almost all Git questions on Stack Overflow involve the CLI.
From the volume of CLI related questions, it’s safe to draw the conclusion the majority of developers predominantly use the CLI to handle their Git repositories.
One reason for it’s popularity is because it provides the extra power necessary when your needs have outgrown even the most complete GUI, and your repo requires some low level, fine-grained TLC.
Anything a GUI can do, the CLI can do (and often better).
Even if your Git GUI of choice is capable of handling your exact situation, you’re likely to have a session of tinkering with menu options and checkboxes to figure out how to address your specific issues. All the while, the GUI will simply be executing CLI commands in the background, so you might as well do it for yourself. If for nothing more than to gain a solid understanding of what’s happening under-the-hood.
One more reason to love Git’s CLI is that it’s been around longer, and has a much larger community, than any Git GUI. Subsequently, there are more people out there who can answer your questions about the Git CLI.
Another shortcoming of Git GUIs shows up when you start using a standard branching model, because these tools often don’t have built-in support for the popular models. However, Atlassian’s SourceTree supports the popular Git-flow model, and it happens to have a pretty useful overview of the different classes of branching models, also known as work-flows.
But what if I want a cool graph of my commits? Can the non-graphical CLI provide that?”
Yes, yes it can. Follow along and you’ll learn how to make it happen.
Cool History Graphs
It turns out the Git CLI can produce useful, almost pretty, commit-history graphs for you. It even lets you filter for the commits you actually want to see.
First, you need a repo with something worth looking at, and for this tutorial you’ll find it inside the git-extras repository.
You’re just going to use this repo as an example to illustrate some CLI usage, but the commands here are quite useful and you may want to install them to enhance Git’s CLI further.
Look at that, one more reason to love the Git CLI — the ability to extend Git via the command line is yet another reason why the Git CLI holds an edge over any Git GUI.
Now use one of Git’s most basic commands, git clone to make a copy of this repo on your local machine. First open a terminal window and navigate to the folder where you want to store the repo, and then type the commands below:
git clone email@example.com:visionmedia/git-extras.git cd git-extras
If you get any errors from the clone command above, you can replace that line by:
git clone https://github.com/visionmedia/git-extras.git
Note: If you do see an error here, it’s likely because you’ve not setup your SSH keys with GitHub. You can find out more about how to do that and why it’s important here
Make sure you’re in the git-extras directory by entering pwd on the command line. Now, take a look at a graph of commits by entering the following command:
git log --graph --pretty=oneline --abbrev-commit
And you should see something like this.
* 3b3efd7 Merge pull request #219 from jhoffmann/patch-1 |\ | * 3c6a084 Update Readme.md * | 47bb6e0 Merge pull request #222 from jsipprell/pull/filenames-containing-spaces |\ \ | * | 1479b96 git effort: handle filenames containing whitespace cleanly | |/ * | 039bb5c Merge pull request #223 from mwoc/master |\ \ | * | 0687de7 Use two-space indents for log entry output, so it again is debian changelog compatible (as before commit 1235e4a5) | |/ * | a87403b Merge pull request #229 from yggdr/master |\ \ | * | e4290df invoke bash via /usr/bin/env for portability * | | 45b3515 Merge pull request #231 from petersohn/master |\ \ \ | |/ / |/| | | * | 698d027 Make git-obliterate work if there are whitespaces in filename |/ / * | d437418 Release 1.9.1 * | ef00c0c Merge pull request #225 from pmalek/master |\ \ | |/ -- And it continues! :
Hit the spacebar a few times; you’ll see pages and pages of commits and a graphical representation of the branches and merges over time. When you’ve soaked in the graph, hit q to quit.
That’s probably a lot more commits than you want to dig through. But you can limit the scope in various ways — for instance, by date.
Enter the command below:
git log --graph --pretty=short --abbrev-commit --since=April
This shows just the commits since April, and it also grabs some useful info about each commit by asking for a short format instead of oneline.
Now you see a much shorter graph like this one.
* commit 3b3efd7 | Merge: 47bb6e0 3c6a084 | Author: hemanth.hm | | Merge pull request #219 from jhoffmann/patch-1 | * commit 47bb6e0 |\ Merge: 039bb5c 1479b96 | | Author: hemanth.hm | | | | Merge pull request #222 from jsipprell/pull/filenames-containing-spaces | | | * commit 1479b96 | Author: Jesse Sipprell | | git effort: handle filenames containing whitespace cleanly | * commit 039bb5c |\ Merge: a87403b 0687de7 | | Author: hemanth.hm | | | | Merge pull request #223 from mwoc/master | | | * commit 0687de7 | Author: Maarten Winter | | Use two-space indents for log entry output, so it again is debian changelog compatible (as before commit 1235e4a5) | * commit a87403b |\ Merge: 45b3515 e4290df | | Author: hemanth.hm | | | | Merge pull request #229 from yggdr/master | | | * commit e4290df | | Author: Konstantin Schukraft | | | | invoke bash via /usr/bin/env for portability | | * | commit 45b3515 |\ \ Merge: d437418 698d027 | |/ Author: hemanth.hm |/| | | Merge pull request #231 from petersohn/master | | | * commit 698d027 |/ Author: eszabpt | | Make git-obliterate work if there are whitespaces in filename | * commit d437418 | Author: TJ Holowaychuk | | Release 1.9.1 | * commit ef00c0c | Merge: e91da7c 4145f22 | Author: TJ Holowaychuk | | Merge pull request #225 from pmalek/master | * commit 4145f22 | Author: Patryk Małek | | Fixed git-changelog errors when multiple files match change|history | * commit de6463f Author: Patryk Małek Fixed git-changelog errors on first usage (END)
Don’t forget to hit q to exit.
Next, filter your graph to show only commits by a specific user. Graph all the commits by Patryk Małek since April, and list a bunch more info on each commit by entering the following command:
git log --graph --pretty=fuller --abbrev-commit --since=April --author="Patryk Małek"
Now you have a shorter, much more useful list of commits with quite a lot of detail.
* commit 4145f22 | Author: Patryk Małek | AuthorDate: Mon Jun 16 16:32:47 2014 +0200 | Commit: Patryk Małek | CommitDate: Mon Jun 16 16:32:47 2014 +0200 | | Fixed git-changelog errors when multiple files match change|history | * commit de6463f Author: Patryk Małek AuthorDate: Mon Jun 16 16:27:57 2014 +0200 Commit: Patryk Małek CommitDate: Mon Jun 16 16:27:57 2014 +0200 Fixed git-changelog errors on first usage
Maybe you’d like a list all of the accepted pull-requests since June? Just enter the following:
git log --grep="pull request" --since=21june
Since GitHub automatically adds a useful message each time an owner of a repo accepts a pull request, you see something like this:
commit 3b3efd74831639a7eeac37add0a1332a0ca9e847 Merge: 47bb6e0 3c6a084 Author: hemanth.hm Date: Sun Jul 6 12:37:41 2014 +0530 Merge pull request #219 from jhoffmann/patch-1 Update Readme.md commit 47bb6e0f7423304caf214998e44e717400b30bad Merge: 039bb5c 1479b96 Author: hemanth.hm Date: Sun Jul 6 12:36:08 2014 +0530 Merge pull request #222 from jsipprell/pull/filenames-containing-spaces git-effort handles filenames with whitespace. commit 039bb5c318f329d62cff86d3a11b4c516586b963 Merge: a87403b 0687de7 Author: hemanth.hm Date: Sun Jul 6 12:34:21 2014 +0530g Merge pull request #223 from mwoc/master Restoring debian changelog compatibility. commit a87403b57f3c72034be2abc066d40425e8a9a7ba Merge: 45b3515 e4290df Author: hemanth.hm Date: Sun Jul 6 11:55:00 2014 +0530 Merge pull request #229 from yggdr/master /usr/bin/env for portability. commit 45b3515ff811e603c2c9278553af26c7c996c117 Merge: d437418 698d027 Author: hemanth.hm Date: Sun Jul 6 11:52:58 2014 +0530 Merge pull request #231 from petersohn/master git-obliterate now supports whitespaces in filename. commit ef00c0c2c6d146081347dd75d27ee6a9facb4ee9 Merge: e91da7c 4145f22 Author: TJ Holowaychuk Date: Sat Jun 21 21:53:16 2014 -0700 Merge pull request #225 from pmalek/master Fixed git-changelog errors on first usage (END)
Even the Best Tools Aren’t Without Fault
OK, the Git CLI is cool and powerful, but you might feel that:
- There’s a bunch of typing, especially if you’re using a formal branching strategy.
- It’s easy to misspell parameters, options, names of branches, remotes, files and commit hashes.
- Here’s the clincher: It’s easy to complete hours of work, only to find that you’ve been working on and committing to — and possibly even force-pushing — #gasp — the WRONG BRANCH.
- All in all, the CLI involves a lot more stuff to remember than just sticking with a GUI.
Stay with me; you’ll see the true value of the CLI in a few moments.
Those are fair points. Any type of GUI that’s outstandingly effective uses menus to organize the functionality servicing the myriad of use cases. Why do GUIs use menus?
Run an Internet search for ‘recognition vs. recall‘ and you’ll see a ton of results that talk about how much faster and more efficiently your brain recognizes something than it recalls something.
For example, pretend you’re trying to remember the name of that one actor who played a supporting role in your favorite movie of 2014, without peeking at your phone. Unfortunately, you just can’t seem to remember.
You could probably pick his name out of a cast list, because your marvelous brain will recognize the name even though it couldn’t recall it.
That’s precisely why GUIs use menus. Users can easily scan menus and recognize the item they want. You can actually use the same idea on the command line.
If you’ve used the command line to navigate around your file system, you probably use a combination of recall and recognition to navigate.
Imagine that you’re typing
cd ~/D[tab][tab] (recalling the first letter), and you’ll see a list of items in your home folder that start with the letter D. Then recognizing the name from that list, you type a few more characters, and then
[tab] to finish out the directory name.
Everything seems a little easier now, doesn’t it?
Meet your new friend autocompletion, who will help you by offering your brain something to recognize, rather than recall. It’s the next really cool Git CLI tip you’re going to learn all about.
Autocompletion – It’s Not Just For Navigation Anymore
If you’re an experienced terminal user, you probably have a totally awesome shell that you absolutely love, and it’s probably bash since that’s the default shell on OS X. If you use another totally awesome shell, this next section may or may not be relevant, since it’s based on scripts that aim at bash.
Now for the fun stuff! You’re going to install Git bash-completion, aka Git-flow-completion. This provides autocompletion of Git commands, parameters, options, branch names, filenames, remotes — and did I mention branch names??
This is amazingly helpful if you use feature branches. For example, I regularly use branch names that are more than 20 or 30 characters long, because the teams I work with use user-stories for the feature-branch names.
With autocompletion, you’ll prevent brain strain by using command line to help you recognize even the longest, most difficult-to-remember names.
Next up – Homebrew, the power tool that makes installing autocompletion (and more) a breeze.
There are several ways to install Git bash-completion. Since you’re probably a Mac user, this tutorial will use Homebrew for the installation.
What is Homebrew? Are we making beer now?
Well, I’m glad you asked, and no you’re not going to learn how to make a refreshingly crisp ale in this tutorial.
Homebrew is a totally awesome package-manager, written in Ruby and utilising Git. In Homebrew’s own words it “… installs the stuff you need that Apple didn’t.”
Note: As with any software installation, it’s always good to backup your system and data before proceeding.
First check to see if Homebrew is already on your Mac by entering
brew -v in the command-line.
- If you see something like
Homebrew 0.9.5, then you already have Homebrew.
- However, if you see
Command not found, then you don’t have Homebrew installed. You’re going to change that right now.
Installing Homebrew is stunningly easy. Enter the following into your command line:
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
This will download and run a Ruby script.
Note: If you’re running Snow Leopard or later, by default you should have a fairly modern and stable version of Ruby on your machine. However, it’s recommended that you run the latest versions of Mac OS X and Ruby.
If you’re curious about what Homebrew’s installed, look in the directory /usr/local/Cellar/.
Updating Homebrew and Playing Doctor
Now that you have Homebrew installed, make sure all of the brew formulae are up to date as there are continual updates, pretty much every day. To bring it up to speed, just enter brew update at the prompt and give it a minute or so, as there are many formulae out there.
Optionally, you can use Homebrew to clean up your environment by entering brew doctor.
For this tutorial, it’s not mandatory and can feel a bit squirrelly because it will tell you about any oddities it finds in your system environment and tell you how to fix them. However, the good doctor’s output can be very helpful when you’re looking for a clue about why an installation went awry.
You’re almost there! Now that you have installed and updated Homebrew, you’re going to install the magical autocomplete tool. Enter the following command at your prompt:
brew install git bash-completion
You’re not quite finished yet. Next, you need to change your bash profile so that bash-completion loads whenever you open the terminal. Follow these instructions.
- Open a terminal window, and navigate to your home folder:
- Check to see if the file .bash_profile exists in your home folder:
- If the file is missing, create it:
- Open the file in your favorite text editor:
- At the end of the file (unless you have a reason to put it elsewhere), enter the following:
- Close your editor and the terminal window.
if [ -f `brew --prefix`/etc/bash_completion ]; then . `brew --prefix`/etc/bash_completion fi
Test It Out!
Open a new terminal window. Just like in bash, autocompletion happens when you type the
[tab] key. So, start typing
git [tab][tab] and be sure to type a space after ‘git’ before hitting the tab button. You should see the output below.
~ $ git add column get-tar-commit-id pull shortlog am commit grep push show annotate config gui rebase show-branch apply credential help reflog stage archive credential-osxkeychain imap-send release start bisect deliver init relink stash blame describe instaweb remote status branch diff log repack submodule bundle difftool loggraph replace subtree checkout fetch merge report svn cherry filter-branch mergetool request-pull tag cherry-pick finish mv reset whatchanged citool format-patch name-rev revert clean fsck notes rm clone gc p4 send-email ~ $ git
Sweet! A list of all the Git commands pops up, and you can continue typing where you left off.
Note: If you get error messages instead of the cool list above, then run
brew doctor, as mentioned earlier. Fixing the issues the good doctor finds should solve your problems. You’ll know everything is fixed when brew doctor gives you the message “Ready to Brew.”
A conflict with the Git installed by Xcode is a common issue. Try overwriting that Git using
brew git install followed by
brew link git.
If there are problems, brew doctor will provide you with instructions to fix them.
Now navigate to a Git repository (Navigate to the git-extras folder you installed earlier if you don’t have any repos of your own).
git bra[tab], and you’ll see that autocompletion recognizes this as
git branch. Continue typing
ma[tab], and autocompletion extends this to git branch master. YAY!
[ctrl]-c out of that and try a different command. Type
git log --gr[tab][tab] and you see the two available flags. Continue typing
Nice! You have a graph of your most recent commits! Now use autocomplete to run the following verbose command quickly, typing only as much as is necessary to trigger autocomplete:
log --graph --pretty=oneline --decorate --abbrev-commit
Wait, Wait! Wasn’t there something about remembering what branch I checked out locally?
Yes, thanks for reminding me. Is it even possible for a command-line environment to keep the user informed in real time? Well, what about the prompt?
The prompt is a truly excellent way to keep you informed about the state of the local repo that’s in your current working directory, especially if you have multiple terminals open inside multiple repos.
A Branch in Your Prompt
Before continuing, are you curious to see what your current prompt really is?
In your terminal window, enter
echo $PS1, and you’ll see the prompt’s definition. The default prompt for bash is
When you installed the completion script, you also installed the bash-prompt script as well. So now it’s pretty easy to set up a custom prompt to show your repository’s active branch.
In your terminal window, enter the following. (Note that there is no space on either side of the “=” sign, and that there are two underscores at the beginning of
PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
Now you should have a prompt that looks something like this.
[jeff.wolski@Jeff-Wolskis-MacBook-Pro git-extras (master)]$
Well, the branch is there, but this prompt is not as useful as it could be. Enter each line below (one at a time) into your terminal command line, and observe what happens to the prompt.
Start with just the branch name in the prompt using the function
"%s" is a placeholder for the value returned from the function.
You need some space between the prompt and the cursor, so add a space after the function call.
PS1='$(__git_ps1 "%s") '
Now add a $ near the end.
PS1='$(__git_ps1 "%s") \$ '
How about some parenthesis around the branch name?
PS1='$(__git_ps1 "(%s)") \$ '
Some people like to see the present working directory, so add
\w to the beginning of the prompt.
PS1='\w $(__git_ps1 "(%s)") \$ '
But a nicer format might be to use
\W instead (it’s much less verbose).
PS1='\W $(__git_ps1 "(%s)") \$ '
Many people switch between users and like to the current one to be in the prompt. Add
\u to the beginning of the prompt now.
PS1='\u \W $(__git_ps1 "(%s)") \$ '
If you appreciate a reminder of what machine you’re on, add the machine name to the prompt with
PS1='\u@\h \W $(__git_ps1 "(%s)") \$ '
Perhaps you need a timestamp for each command as you look back in your terminal history? Put a date function into your prompt with:
PS1='$(date +%k:%M:%S) \W $(__git_ps1 "(%s)") \$ '
Now close the terminal window you’ve been using and then open a new one. You’ll discover the prompt you just created is no longer there!
If you create a prompt you’d like to be the default for all your terminal windows, type it into the .bashrc file in your home directory — create the file with
touch .bashrc if it does not yet exist. The bash shell reads the ~\.bashrc file whenever it starts up.
Where To Go From Here
Congratulations! You’ve set up Git completion on your machine, which should save plenty of brain-power, time and typing. By completing this Git tutorial, you also learned how to create a customized prompt with the branch name included.
Play around with different options for a custom prompt until you find one that’s right for you. You can find more bash prompt options here.
Also, Homebrew is a fantastic tool you can use to install any number of other command line enhancements. Learn more about Homebrew to discover other ways it might enrich the quality of your life :].
Finally, check out How To Use Git Source Control with Xcode in iOS 7, if you haven’t already.
Thanks for working through this Git tutorial! If you have questions, comments or just want to share your discoveries, leave your notes in the comments below.