This is for those who haven't used git before and need a crash-course on basic operations. I'll keep this as simple as possible, and focus specifically on crawl-ref, rather than git in general. I've also added links to the official git docs at the end, which you can read instead of, or in addition to this, if you're inclined.
I strongly recommend using at least git 1.6 or later. While you can use older versions, the newer versions are much more user-friendly. This guide assumes you're using 1.6.
sudo port install git-core
).Set your name for git to use when committing changes:
$ git config --global user.name "John Doe"
Set your email address:
$ git config --global user.email "jdoe@us..."
This assumes that you're using git only for crawl-ref, and you don't mind making your id the default email id for all git work. If you're already using git for other things, you don't need this crash-course anyway. :P
Developers (anyone with commit rights to the git repo):
$ git clone ssh://git@github.com/crawl/crawl.git
Other users:
or by https:
$ git clone https://github.com/crawl/crawl.git
git clone
clones the crawl git repository to your machine. When it's done, you have a full local copy of the crawl repository with all its history.
Next you need to get the source/contrib submodules
. Go into the top-level crawl directory and do
$ git submodule update --init
When you come back to a git repository after a while, you may not recall where you cloned it from. You can check with:
$ git remote -v
For me, this reports:
origin ssh://dshaligram@github.com/crawl/crawl.git (fetch) origin ssh://dshaligram@github.com/crawl/crawl.git (push)
See all available branches in the repository:
$ git branch -a
See all tags:
$ git tag
Use
$ git pull
to grab the latest commits from the remote repository.
git pull
assumes your working tree is clean; it will refuse to overwrite any files that you've modified locally (unlike svn's svn update
, which will happily try to modify the file anyway, and add conflict markers if there are conflicts).
If git pull
fails because you have local changes, you have two options:
stash
) your local changes, pull changes from the remote repository, and reapply your local changes:$ git stash # this saves your local changes $ git pull # grab changes from remote $ git stash apply # reinstate your local changes
If your local changes conflict with the changes from remote, git stash apply
will warn you of the conflict and add conflict markers to the relevant files.
If git status
informs you that one or more source/contrib
directories are out of date, you can pull changes to them by going to the top level crawl
directory and doing:
$ git submodule update --init
git's master branch is the equivalent of svn trunk. Immediately after cloning a repository, the cloned crawl-ref repo will be on the master branch. You can check what branch you're working on at any time with:
$ git branch
The active branch will be asterisked.
Before starting work, let's make sure our working copy is not dirty:
$ git status # On branch master nothing to commit (working directory clean)
Looking good. For our first change, let's assume we're fixing a bug in, say, stuff.cc.
$ vim stuff.cc <hackhackhack> $ make <test; that bugfix rocks the world>
Let's check how git sees things now:
$ git status # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: stuff.cc # no changes added to commit (use "git add" and/or "git commit -a")
git sees that we've modified stuff.cc. You can see what changes you've made with:
$ git diff
Time to actually commit the change:
$ git commit -a
git will bring up your preferred editor for you to enter your commit message. On Windows, you may have to set your EDITOR
environment variable; alternatively, you can specify your commit message on the command line:
$ git commit -a -m "Fitted stuff.cc with warp drive"
When you're done, your change has been committed to your local repository. Let's try git status
again:
$ git status # On branch master # Your branch is ahead of 'origin/master' by 1 commit. # nothing to commit (working directory clean)
So git's telling us that we have one local commit that we haven't sent to the remote git repository yet. Let's send in our fix (core devs only):
$ git push
You will be prompted for your password again. git should then push your commit, producing a last line that looks like:
54ea5f1..ec2e15e master -> master
The exact commit ids will differ, but a successful push looks like this. Your push may fail if someone has pushed changes already (git will warn you about a non-fast-forward). In this case, just pull and push again:
$ git pull $ git push
You can see a history of changes with
$ git log
You can see the history for a particular file by doing
$ git log filename
or see a list of changes for all files in a directory by doing
$ git log directory-name
To see the diffs that were made with each change, and not just the commit comments, you can add -u
or -p
to the command line:
$ git log -u
git log
by itself may make it appear that the change history is a straight line, but we know that git can handle branching histories. We can request that git log
show us the branching history with:
$ git log --graph
You can also view history using the gitk GUI (this is installed by default; everyone should have it, and I recommend it):
$ gitk
The history commands normally show you the history of the current branch, but you can view other branches/tags' histories by naming the branch or tag:
$ gitk origin/stone_soup-0.2 $ gitk release-0.5.1
If you want to annotate a file with last author and commit to change each line in the file, you can use git blame
, which is similar to svn blame
:
$ git blame stuff.cc
So far we've restricted our attention to “master”, which is the easiest branch to work with, since it's selected by default. Now let's say we have a bug report with a 0.5.1 save, and we need the 0.5 code to test the save with.
Let's take a look at the branches we have locally:
$ git branch * master
The only local branch in our repository is master. Let's look at the branches in the remote repository:
$ git branch -r origin/HEAD -> origin/master origin/master origin/stone_soup origin/stone_soup-0.1.3 origin/stone_soup-0.1.4 origin/stone_soup-0.1.5 origin/stone_soup-0.1.6 origin/stone_soup-0.1.7 origin/stone_soup-0.2 origin/stone_soup-0.3 origin/stone_soup-0.4 origin/stone_soup-0.5
So our local repository has a “master” branch corresponding to the remote “master” branch. We do not yet have a branch corresponding to the remote “stone_soup-0.5”, so let's create the branch and switch to it:
$ git checkout -b stone_soup-0.5 origin/stone_soup-0.5 Branch stone_soup-0.5 set up to track remote branch stone_soup-0.5 from origin. Switched to a new branch 'stone_soup-0.5'
master and stone_soup-0.5 are both local branches now:
$ git branch master * stone_soup-0.5
git status
will also confirm that we're on 0.5 now:
$ git status # On branch stone_soup-0.5 nothing to commit (working directory clean)
A quick peek at the history to make really sure we're on 0.5:
$ git log
To grab the latest changes for 0.5:
$ git pull
Ok, now we compile 0.5, test the 0.5 save and verify that a bug exists. Once we've fixed the bug, we create a commit:
$ git commit -a
Check git status
:
$ git status # On branch stone_soup-0.5 # Your branch is ahead of 'origin/stone_soup-0.5' by 1 commit. # nothing to commit (working directory clean)
Right, we're ready to push our fix. But now that we have multiple local branches, let's first ask git what it plans to do when we push:
$ git push --dry-run -v Pushing to ssh://dshaligram@cr.../gitroot/crawl-ref/crawl-ref To ssh://dshaligram@cr.../gitroot/crawl-ref/crawl-ref = [up to date] master -> master b05bb66..976e722 stone_soup-0.5 -> stone_soup-0.5
So git wants to push the local master and stone_soup-0.5 branches to the corresponding remote branches; the master branch has no new local changes, whereas the 0.5 branch does (the new change is 976e722).
In older versions of git, by default, git push
will push *all* your local branches to the corresponding branches on Github. This is important to remember; if you had local commits on master, they would also be pushed. This behaviour can be changed (the option is called push.default
) if it bothers you.
Once you're done working on 0.5, you can switch back to master
with:
$ git checkout master
The next time you need to work on 0.5 again, you can return to it with:
$ git checkout stone_soup-0.5
If you've been following along with the examples, you've probably noticed that all the password prompts when you push or pull from github are wearing on your patience (this does not apply to users who've cloned using the git:// or https:// URL).
svn handles the problem of needing passwords all the time by caching the credentials you use the first time you enter them (for https:// WebDAV svn access; svn does not cache credentials for svn+ssh). git does not cache credentials by default.
To save yourself the pain of entering your password each time, you should create an ssh key and upload your public key to github.
Let's create an ssh key (if you already have a key, skip this step):
$ ssh-keygen
You can accept all the default options and use an empty passphrase for convenience (don't do this on an account you share with other people, or they can commit to crawl-ref too :P)
Once the key is generated, you'll have two files, id_rsa
and id_rsa.pub
, in your ~/.ssh
(the .ssh directory in your home directory). id_rsa
is your private key, and should not be shared or given to anyone else (or they can pretend to be you); id_rsa.pub
is your public key, and this is what you'll upload to Github.
Go to github, log in and go to your profile, click “SSH keys”, then “Add ssh key”.
Copy the one line in your id_rsa.pub
, and paste it into the text area. Hit Save when you're done.
Within a few minutes of uploading your key, you should be able to push/pull from github without needing to reenter your password.
Windows users (msysgit) can follow these exact same steps in a git bash prompt.
You can also create a DSA key instead of an RSA key (or use an existing DSA key). Github accepts both.
It often happens that you make a change to a file that you didn't want, or accidentally delete files that you did want. You can revert a file to its pristine version as:
$ git checkout stuff.cc
This also works to bring back files you accidentally deleted. If you forget these commands, git status
will remind you:
$ git status # On branch master # Changed but not updated: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: mt19937ar.cc # no changes added to commit (use "git add" and/or "git commit -a")
When committing changes with git commit -a
, newly created files won't be added to the commit unless you request it with git add
. Let's take an example:
$ git status # On branch master # Untracked files: # (use "git add <file>..." to include in what will be committed) # # dwim.cc nothing added to commit but untracked files present (use "git add" to track)
Before we commit, we must add the new file:
$ git add dwim.cc $ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: dwim.cc $ git commit -a
In general, git status
is your friend. It will usually tell you exactly what you need to do.
Let's say it's time to create a stable 0.6 branch. Here's how you'd do it:
a) Branch 0.6 from master:
$ git checkout -b stone_soup-0.6 master
Let's check our local branches now:
master stone_soup-0.5 * stone_soup-0.6
stone_soup-0.6 is currently a *local* branch. The remote git repo doesn't have it yet.
b) Push the local branch with a –dry-run
first to make sure we're not lousing up anything:
$ git push --dry-run -v origin stone_soup-0.6 Pushing to ssh://dshaligram@cr.../gitroot/crawl-ref/crawl-ref To ssh://dshaligram@cr.../gitroot/crawl-ref/crawl-ref * [new branch] stone_soup-0.6 -> stone_soup-0.6
Now for the real push:
$ git push origin stone_soup-0.6
c) Point your local branch at the new remote branch:
Now that we've created the remote branch, we want to set up our local “stone_soup-0.6” to get changes from the remote “stone_soup-0.6” when we do a git pull
. We do this as:
Tell git where to find stone_soup-0.6's remote repository:
$ git config branch.stone_soup-0.6.remote origin
And that the corresponding remote branch is stone_soup-0.6:
$ git config branch.stone_soup-0.6.merge refs/heads/stone_soup-0.6
Now you can pull changes from the remote 0.6 branch:
$ git pull
This is only necessary for new branches that you create locally and push to origin. This configuration is automatically set up for you when you pull branches that already exist on origin.
Once 0.6 is fit for a release, you'll need to tag it:
$ git checkout stone_soup-0.6 $ git tag -a release-0.6 -m "0.6: Now with extra kangaroos"
Once we create a stable branch (say 0.5), we usually make all changes to trunk, but apply bugfixes to the 0.5 branch as well. You can apply changes from master to stone_soup-0.5 with git cherry-pick
:
$ git checkout master [ make the changes for the bugfix ] $ git commit -a $ git checkout stone_soup-0.5
And cherry-pick the tip commit of the master branch (the bugfix we just committed there):
$ git cherry-pick master
If the commit you want to cherry-pick is not the tip of a branch, just use the commit's id:
$ git cherry-pick b0ed1449
git push
will then send in the commits from both branches.
I've intentionally kept my examples very simple and glossed over a lot of details. I've covered the common operations in the svn workflow, but git can do much more for you if you spend a little time learning it.
git documentation central: http://git-scm.com/documentation
git tutorial: http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html
Another crash course for svn users: http://git-scm.org/course/svn.html
Introduction to git's internal workings (useful for actually understanding what's going on): http://www.newartisans.com/2008/04/git-from-the-bottom-up.html