24 May 2017

Written by Jon Bevan

I get the impression that Git is still quite misunderstood, and newcomers to Git (myself included) struggle to pick up which commands are actually useful and which ones were just designed by Linus to invoke fear, uncertainty and doubt.

Over the last few years I've come up with (and stolen) a few Git command aliases that make my day-to-day usage of Git on the command line an intuitive/fluent experience.

If you've been using Git for a bit, but don't really grok it, I'd recommend watching Knowledge is Power: Getting out of trouble by understanding Git if you haven't already.

TL;DR

Here's my Git alias list from ~/.gitconfig.

[alias]
  co = checkout
  cm = checkout master
  cb = checkout -b
  st = status
  staged = diff --staged
  unadd = reset HEAD
  save = commit -m
  amend = commit --amend
  discard = checkout --
  fp = !git fetch -a --tags && git pull
  please = push --force-with-lease
  graph = log --oneline --all --graph --decorate
  oneline = log --oneline -n 20
  hist = log -n 20 --date=iso --pretty=format:'%C(yellow)%h%Creset %cd %Cblue%an <%ae>%Creset %s'
  refresh = "!f() { CBRANCH=$(git rev-parse --abbrev-ref HEAD); git cm && git fp && git co \"$CBRANCH\" && git rebase master; }; f"
  put = "!f() { CBRANCH=$(git rev-parse --abbrev-ref HEAD); git push -u origin \"$CBRANCH\"; }; f"

Downloading a repository

git clone <repository url here>

We're literally cloning the entire contents of the remotely hosted repository.

In your terminal, switch into the directory that you want your repository downloaded into, then copy & paste the SSH or HTTPS url for the repository and run this command.

cd ~/Code
git clone git@github.com:facebook/react.git
cd react

Now you're in the directory for your local copy of the React project.

See git clone for more options.

Creating a branch

git checkout -b <your branch name here>

I always make changes on a feature branch, and we enforce branch naming conventions too, so my workflow looks like:

git checkout -b bugfix/PROJECTKEY-123-fix-all-the-things

However, checkout -b is a lot of typing, so I use this alias in my ~/.gitconfig file:

[alias]
  cb = checkout -b

All further aliases in this blog post can be added below the [alias] section of your ~/.gitconfig file.

So I can type:

git cb feature/PROJECTKEY-456-awesome-new-sauce

See git checkout for more options.

Checking repository status

git status

I use this A LOT. This command will tell you useful information about the files in your local copy of the repository, like which files are modified, which files are staged ready to commit, etc.

It also prints out some useful commands as hints for what you might want to do next.

Obviously, status is a lot of typing, so I use this alias:

st = status

So I can type:

git st

See git status for more options.

Switching branch

git checkout <branch name here>

If you have no unstaged changes (see below) in your directory, you can just 'checkout' the branch you're interested in.

I use these aliases:

co = checkout
cm = checkout master

So that I type:

git co bugfix/FOO-123-everything-is-on-fire
# now I'm on that fire related branch
git cm
# now I'm on the master branch

See git checkout for more options.

Stage changes ready for a commit

git add -p

This command allows you to pick and chose which changes you've made should be staged for a commit. It will show you a diff of each partial change and ask if you want to stage that change or not.

Press y for YES and n for NO or q for QUIT when it asks you if you want to Stage this hunk. If you just press enter it gives you a whole load more info about each response option.

I don't have an alias for this.

I usually run git st (see above) after I've run git add -p to sanity check I've staged the changes I think I have!

I also use git staged (below) to complete my sanity check.

See git add for more options.

Reviewing staged changes before committing

git diff --staged

This shows a diff of all the changes I've staged for the commit.

I use this alias:

staged = diff --staged

So that I can type:

git staged

See git diff for more options.

Un-staging changes

git reset HEAD -p

I often stage a change to a file that I don't actually want to commit. I have fat fingers.

This handy command does the exact opposite to the git add -p command listed above, it asks you bit by bit whether you want to remove a change from staging.

I use this alias, because its like the synonym for git add:

unadd = reset HEAD

And then type this command when I want to remove staged changes:

git unadd -p

NB - this does not remove those changes from disk, it just removes them from being staged for a commit.

See git reset for more options.

Making a commit

git commit -m "<your commit message here>"

Once I'm happy with the changes I've staged for a commit, this is how I make a commit.

In my pursuit of reduced typing I use this alias:

save = commit -m

So my commands look like:

git save "Updates..."

Unlike that example, I highly recommend you write good commit messages.

See git commit for more options.

Viewing commits

git log -n 10 --oneline
git log -n 10 --oneline -p
git log -n 10 --date=iso --pretty-format:'%C(yellow)%h%Creset %cd %Cblue%an <%ae>%Creset %s'
git log --oneline --all --graph --decorate

I use a combination of those commands or their respective aliases that I've set up in order to see what a mess I've made of my Git repository before pushing changes to a remote Git server.

The first command there is fairly simple, one-commit-per-line output containing just the hash and the commit message.

The second command shows the diff that the commit introduces.

The third command shows the author and commit timestamp as well as the hash and commit message.

The fourth command shows a pretty graph view of the repository and how the branches interact.

I use these aliases, in this order of frequency:

hist = log -n 10 --date=iso --pretty=format:'%C(yellow)%h%Creset %cd %Cblue%an <%ae>%Creset %s'
oneline = log --oneline -n 10
graph = log --oneline --all --graph --decorate

See git log for more options.

Amending commits

git commit --amend

When I'm not writing perfect code, I find a bug or typo or something after I've make a commit. This command allows me to stage a new change and then add that to the previous commit.

(This commit actually replaces the previous commit with a new one that is a merge of the new and previous changes.)

I use this alias:

amend = commit --amend

See git commit for more options.

Deleting changes I don't want

git checkout -- <file path here>

Once I've committed my changes, if there are edits I've made to files that I really don't need any more, I run this command to remove those changes from disk. This saves me opening each altered file and doing CTRL-Z or manually undoing those un-needed changes.

I use this alias:

discard = checkout -- 

Which can be used like this:

git discard src/main/java/com/acme/foo/bar/utils/DefaultServiceInstanceFactoryManagerBuilderImpl.java

See git checkout for more options.

Pushing commits to the remote Git server

New branches

git push -u origin <your branch name here>

This tells Git where on the remote Git server (referenced by the word 'origin' which is just the default name for the original remote server you cloned the repository from) to put your branch. It is possible to have a branch called one thing locally and another thing on the remote server.

I use this alias:

put = "!f() { CBRANCH=$(git rev-parse --abbrev-ref HEAD); git push -u origin \"$CBRANCH\"; }; f"

So that saves me copy and pasting the branch name.

Existing branches

git push

If your branch existing on the remote server already, just use this. No alias required.

Rebased branch or a branch with amended commits

git push --force-with-lease

If you've rebased (see below) or amended any commits that you've already pushed to a remote Git server, this tells Git to override whatever is already on the remote server.

I use this alias:

please = push --force-with-lease

See git push for more options.

Fetching changes from the remote Git server

git fetch -a --tags
git pull

The first command there just asks the remote server for the most up-to-date metadata about the repository e.g. which commits are on which branches, what branches and tags exist.

The second command there downloads the changes from the remote server that correspond to the branch you're currently on.

I use this alias:

fp = !git fetch -a --tags && git pull

See git fetch and git pull for more options.

Rebasing from master

If someone else has already checked out your branch or has made changes to it as well, DO NOT rebase the branch or amend commits because then you will have different and incompatible versions of the same branch which will require one of you to delete your local branch completely.

This requires a few steps:

git checkout master
git fetch -a --tags
git pull
git checkout -
git rebase master

Often, you'll be working on a branch and changes will happen on the master branch whilst your working. You have two options on how to reconcile those changes with the changes you have made:

  • Merge master into your branch
  • Rebase your branch off master

We typically prefer rebasing for a couple of reasons:

  • it doesn't introduce lots of additional merge commits
  • it avoids some edge-cases where commits can get lost
  • it makes the Git history a bit more clear, even if it no longer reflects how/when the work was actually done

You can think of rebasing as taking some commits and re-applying the changes that those commits hold elsewhere, so that new commits get generated that look very similar to the original ones.

Whilst rebasing you may need to resolve conflicts in the same way that you would when merging.

I use this alias which uses a few of the other aliases too:

refresh = "!f() { CBRANCH=$(git rev-parse --abbrev-ref HEAD); git cm && git fp && git co \"$CBRANCH\" && git rebase master; }; f"

That alias will obtain the current branch name, switch to master, fetch the latest remote changes, download them for the master branch, switch back to the branch I was on and then rebase the branch off of the up-to-date master branch.

Summary

My typical workflow looks something like this:

git cm                              # Start from the master branch
git fp                              # Get the latest changes
git cb bugfix/PROJ-123-ui-is-broken # New branch please
# Edit files here
git st                              # What have I changed?
git add -p                          # Stage some changes
git staged                          # What have I staged?
git unadd -p                        # Oops, I shouldn't have staged that!
git st                              # What's the latest state of things?
git add -p                          # Stage some other change
git diff                            # What have I modified that I haven't staged?
git staged                          # Is this everything I need for this feature?
git st                              # Does this look about right?
git save "UI is not broken"         # Make a commit
# Edit the test files I forgot about
git add -p                          # Stage changes to the tests, ready for commit
git amend                           # Pretend I fixed the tests as part of the original commit
git discard                         # Discard other changes I made but don't need
git put                             # Push the new branch to the remote server
git refresh                         # Rebase off and updated master branch
git please                          # Overwrite the branch contents I pushed a moment ago

And then I create a Pull Request in Bitbucket Server, ready for review!



blog comments powered by Disqus