Summary of Git Operations

The following describes the operations one ordinarily executes with git during source code development activities. 

Starting up

Perform the following git config settings when configuring git for the first time: 

# Your name and e-mail will appear in commits
git config --global user.name "Firstname Lastname"
git config --global user.email "your_email@youremail.com" 
# Use colors if your terminal is capable
git config --global color.ui true 
# Make 'git push' push only the current branch, and not all of them (see the FAQ):
git config --global push.default tracking

Find more defaults to play with here. You may also be interested in the bash completion script (which comes packaged with git in some distributions).

Cloning an Existing Project

The following will clone the afw package to a subdirectory called LSST/DMS/afw:

git clone git@github.com:LSST/afw.git LSST/DMS/afw

Add a New File

echo '# New build system!' > CMakeList.txt
git status                 # See the status of files in the working directory
git status -s	           # The same in format familiar to SVN users
git add CMakeList.txt      # Add a new file to be tracked by git
git status
git commit                 # Commit the changes (the file addition)
git log

Edit a File and Commit the Changes

echo '#more stuff' >> CMakeList.txt
git status       # See that the file is now "dirty"
git diff         # See the changes
git commit -a    # Commit all changes (don't forget the -a!)

View the Commit Tree

You can view the commit tree with graphical tools such as gitk or gitx (for OS X), or do the following on the command line: 

git log	        # see the new state
git log --stat  # also see what has changed

Amending a Commit

Use the following to change the most recent commit message (and more):

git commit --amend

Pushing Upstream

A check of the status will show that the branch is ahead of origin/master by 2 commits. Do a push to make the changes available to everyone–i.e., they become part of the official LSST code history: 

git status
git push

Working with Branches

Create a Branch

View the current branch, create a new branch named tickets/DM-9999, and then check out the new branch:

git branch
git branch tickets/DM-9999
git branch                  # Note that the current branch has not changed
git checkout tickets/DM-9999   # This is like 'svn switch'

or, you can do it in one line:

git checkout -b tickets/DM-9999 HEAD

The above checks out HEAD into a newly created branch tickets/DM-9999 and switches to it. 

Add a Source File

echo "// still empty" > src/image/ExtendedSources.cc
git add src/image/ExtendedSources.cc
git commit
git log

Switch Branches

After adding a source file (as above), you may switch branches like so: 

ls -lrt src/image/     # Note the new file is there
git checkout master    # Switch to branch 'master'
ls -lrt src/image/     # Note the file is absent

List Available Branches 

The following are variations for listing available branches: 

git branch       # List local branches
git branch -r    # List remote branches
git branch -a    # List all branches (both local and remote)

Making Branches Available

The following will push the branch tickets/DM-9999 to the remote repository 'origin' and set it up so we can pull from the remote branch in the future: 

git push -u origin tickets/DM-9999

Use "git pull --rebase" instead of just "git pull" when working on a branch with someone else; this will avoid unnecessary merge commits without rewriting any history that has already been pushed.

Checking out and tracking an existing branch from an upstream repository

Given a branch tickets/DM-8888 that resides in the remote repository "origin", you can check out that branch with the following: 

git fetch                             # make sure we're in-sync with remote repositories
git checkout -t origin/tickets/DM-8888   # Checks out the branch into a local tracking branch of the same name

Note: newer versions of git allow just 

git checkout tickets/DM-8888

Alternatively, the following checks out tickets/DM-8888 from remote 'origin' into a local tracking branch named 'multifit': 

git fetch
git checkout -t -b multifit origin/tickets/DM-8888

Listing commits on a branch

List commits reachable from 'tickets/DM-8888' that are are not reachable from the master. This will exclude any commits that were merged to the ticket from master. 

git log origin/master..origin/tickets/DM-8888

Now display the differences caused by the above commits. Note: there are 3 dots in this syntax, which is unique to git diff

git diff origin/master...origin/tickets/DM-8888

Merging

To merge your work on a branch back to master, first ensure that you've checked out master, and that your source is up-to-date. Then perform the merge, in this case the branch tickets/DM-9999

git checkout master
git pull
git merge --no-ff tickets/DM-9999
ls -lrt src/image/             # Note the new file is here

Now verify the merge, then upload the changes to the main LSST repo: 

git log
git log --graph # This is better
git push

Tagging

Tagging a package, as might be done for a release, is done using the -a or -s options: 

git tag -a 5.0.0.0      # Create an annotated tag (a tag with a message)
                        # or
git tag -s 5.0.0.0      # Create a gpg-signed tag

You can use -m MSG with -a to save, starting an editor. Note: you must use -a or -s otherwise git describe will ignore your tag. Then finish with: 

git log --graph --decorate  # See the tag you just made
git push --tags             # Push all your tags upstream

Git Tips

Oops, I should have done that on a ticket branch

The following tip is adpted from http://schacon.github.com/git/git-reset.html

I thought it was going to be a tiny bug-fix that I could commit straight to master but it grew into something that should be done on a ticket. 

The following fix is for when you have already committed your changes, but have not yet pushed them: 

$ git checkout master
Already on 'master'
Your branch is ahead of 'origin/master' by 5 commits.

Remember that number 5.

If you're making a new ticket for a fix, 

$ git branch tickets/DM-9999
$ git reset --hard HEAD~5
$ git checkout tickets/DM-9999

and keep working as usual. If you want to apply your commits to an existing ticket branch, 

$ git branch temp
$ git reset --hard HEAD~5
$ git checkout tickets/DM-2019
$ git merge temp
$ git branch -d temp

Be aware that the above will also merge all other commits to master into your ticket branch.

Some useful command-line prompt hacking

Git is distributed with a shell script, contrib/completion/git-prompt.sh, that defines a function, __git_ps1 that's useful for displaying the branch and status of a git repository in your command-line prompt. This file seems to be generally installed by distributors, and so you probably already have __git_ps1 defined in your environment. If not, grab that shell script and source it. If you can't get it, or don't want it, then a poor-man's version is supplied, below.

The behaviour of __git_ps1 is configurable with the following environment variables: 

Environment VariableDefine to ValueMeaning
GIT_PS1_SHOWDIRTYSTATENon-empty* indicates unstaged changes and + indicates staged changes
GIT_PS1_SHOWUNTRACKEDFILESNon-empty$ indicates the presence of untracked files
GIT_PS1_SHOWUPSTREAMauto

< indicates you're behind the upstream and can merge

> indicates you're ahead of the upstream and can push

<> indicates you've diverged

= indicates there's no difference

The result is something like: 

user@machine:~/LSST/afw (tickets/DM-1234>) $

(i.e., I'm on branch tickets/DM-1234 with changes committed that I can push) but all you have to do is add $(__git_ps1) at the desired location in your current PS1 definition. You can source the following script: 

# The following two functions provide a basic alternative for git.git/contrib/completion/git-prompt.sh
# in case it's not available
function prompt_git_dirty {
    local gitstat=`git status 2> /dev/null`
    local charstat=""
    [[ -z $(echo $gitstat | grep "nothing to commit") ]] && charstat="\%"
    [[ -n $(echo $gitstat | grep "Your branch and '.*' have diverged") ]] && echo "${charstat}\<\>" && return
    [[ -n $(echo $gitstat | grep 'Your branch is ahead of') ]] && echo "${charstat}\>" && return
    [[ -n $(echo $gitstat | grep 'Your branch is behind') ]] && echo "${charstat}\<" && return
    echo $charstat
}
function prompt_git_branch {
  git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \(.*\)/[\1$(prompt_git_dirty)]/"
}

# Setup for git.git/contrib/completion/git-prompt.sh
export GIT_PS1_SHOWDIRTYSTATE=1
export GIT_PS1_SHOWUNTRACKEDFILES=1
export GIT_PS1_SHOWUPSTREAM="auto"
type __git_ps1 1>/dev/null 2>&1 || alias __git_ps1=prompt_git_branch

PS1='\[\e[1;32m\]\u@\h\[\e[0;39m\]:\[\e[1;34m\]\w\[\e[1;31m\]$(__git_ps1)\[\e[0;1m\] \$ \[\e[0;39m\]'

FAQ

What is the difference between git commit and git commit -a ?

See  this great explanation here, that I didn't find until I wrote the text below (sigh)...

Committing changes to a git repository is a two-step process:

The two above steps equate to the following commands:

Most version control systems (including SVN and hg) omit Step 1. and always assume you want to commit all files that have been modified. Git is not as presumptuous, because there are sometimes good reasons why you'd want to split the modifications into two different commits (e.g., if you've modified 10 files while developing a new feature, while the one-line modification in the 11th file was an unrelated bug that you stumbled upon and fixed in the process). Now, what if you do want to commit changes to all modified files (or if you're used to SVN behavior and see no point in extra typing)? Then use:

The -a switch tells git to run an implicit git add for all modified files in the working directory, before performing the commit.

How do I restore a file to unmodified state ?

git checkout HEAD myfile.txt

The way to read this command is: 'Dear git, please check out from branch HEAD the file myfile.txt'. In git, HEAD always refers to the current branch. You can probably already tell that writing: 

git checkout otherbranch myfile.txt git

would check out the file from otherbranch. It's even more general than that: instead of a branch name, you can give it any tree-ish out of which to extract the file.

What are best practices for developing on a branch?

Often the features you're developing take a long time to mature. Therefore your feature branch (also sometimes called a "topic branch") may lag behind master quite a lot by the time you're done. What should you do? Should you "sync up" often by merging 'master' into your feature branch, or should you wait and fix any conflicts until the very end?

Junio Hamano has an excellent post on this that is a MUST to read. To summarize:

This strategy minimizes the number of merges in the history of the project, which helps with tools like git bisect (automated finding of commits that caused bugs/regressions). And if you are nervous about doing all the conflict resolution at the very end, look into git rerere.

What are 'cache', 'index', and 'staging area'?

To first (and second, and probably third) order, they're the same: the staging area where you place the files (using git add) that are to be a part of the next commit. That there are three terms for one and the same thing is a historical artefact.

I'm having problems with 'git push' and 'non-fast-forward merge' errors

See Handling Git Push Problems.

How can I avoid stepping on people's toes when making changes?

See this page on interacting with git.