The following describes the operations one ordinarily executes with git during source code development activities.
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).
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 |
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 |
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!) |
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 |
Use the following to change the most recent commit message (and more):
git commit --amend |
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 |
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.
echo "// still empty" > src/image/ExtendedSources.cc git add src/image/ExtendedSources.cc git commit git log |
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 |
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) |
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.
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 |
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 |
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 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 |
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.
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 Variable | Define to Value | Meaning |
---|---|---|
GIT_PS1_SHOWDIRTYSTATE | Non-empty | * indicates unstaged changes and + indicates staged changes |
GIT_PS1_SHOWUNTRACKEDFILES | Non-empty | $ indicates the presence of untracked files |
GIT_PS1_SHOWUPSTREAM | auto |
<> 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\]' |
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.
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.
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.
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.
See Handling Git Push Problems.
See this page on interacting with git.