Before You Start

SVN is based on an older version control system called CVS, and its designers followed a simple rule: when in doubt, do like CVS. Git also takes a form of inspiration from CVS, and its designer also followed a simple rule: when in doubt, do exactly the opposite of CVS. This approach lead to many technical innovations, but also lead to a lot of extra headscratching among migraters. You have been warned.

How to Read Me

This page shows Git commands in a bold, fixed-width font: git help. Non-git commands are just fixed width: svn help. Within a command, text you need to add yourself is in italics: git help command. In tables, Git commands are shown on the left with a blue background, SVN commands on the right with grey. If you are in a hurry, just skimming over the tables should give you a good idea about the basics.

Before running any command the first time, it's recommended that you skim through its manual page. Many of the commands have very useful and interesting features (that we won't list here) and sometimes there are some extra notes you might want to know. You can also get help on any Git command by doing git command -h or git help command.

Configuring Git

SVN includes your username in your commits, whereas Git includes your real name and your e-mail address. You should add these before you start using Git:

Things You Should Know

Here are some concepts that will help you understand Git. If you are in a hurry, you can skip this section and come back when you get confused - you should be able to pick up a lot about Git with just your intuition.

Checkouts and Repositories. Each Subversion project has one central repository which stores all the history. Everyone has a checkout of the repository, which they update from and commit to. Each Git project give everyone a personal repository, and lets them pull from and push to each other. Many Git projects use an "official" repository that they agree to treat like an SVN repository.

URLs and paths. In Subversion, a URL identifies the location of the repository, and the path inside the repository identifies the branch or tag. So svn://svn.example.com/trunk might be the URL for the trunk branch of your repository. In Git, the URL is still the location of a repository, but branches and tags are labels you access with Git commands. So git://git.example.com/ might be the URL for a whole Git repository.

Visualize. Git version history can get complicated, especially in big projects. You may like to look at your repository with the gitk viewer to understand what's going on.

Revisions. Subversion identifies revisions with numbers starting at 1 and going up in each revision. That's fine when one central repository keeps track of all the numbers, but who decides the next revision number if everyone has their own repository? Git uses unique 40-character identifiers instead - SHA-1 hashes that you can think of as random strings. These numbers look kinda scary, but you don't actually use them that often. Git has nicknames for the commits you use most often, has relative names for other interesting commits, and lets you refer to the rest by the first half-dozen characters in their identifier. For example, your current commit is always called HEAD, the parent of the current commit is HEAD^ and the grandparent is HEAD^^ (you can go on adding carets). The commit abc123def456abc789def012abc345def678abc9 can be referred to as abc123 (and you can use abc123^, abc123^^ etc.). See the git-rev-parse manpage if you want to know which commit trunk@{'tea-time last Wednesday}~2 refers to.

Creating a New Repository

Let's step through how you create and update a new project in Git and SVN.

If you are putting an existing project under version control, you would cd to your project directory, then:

git initgit add .git commit

svnadmin create myproject_reposvn import . myproject_repo

git init initializes the repository, git add . adds all the files under the current directory and git commit creates the initial import.

Remember - Git does not have separate places for a repository and your working copy - the working copy always already contains the repository as well, in the .git subdirectory. (Of course you can have special repositories that do not have a working copy attached - bare repositories. These are useful for having a repo over at some server where you don't do any actual work but which serves just for pushing and pulling.)

If you are downloading someone else's Git project, you would do this instead:

git clone urlmyproject

svn checkout url

Or to download an SVN project into Git:

git svn clone --stdlayout urlmyproject

svn checkout url

git clone and git svn clone download the complete contents of a remote repository and create a local copy of it. --stdlayout tells Git that it should look in the standard trunk/tags/branches directories for branches (see the git-svn manual if your project uses a different layout).

Now your tree is officially tracked by Git. One nice thing to note - no matter how many subdirectories your project has, there's only one .git directory that contains all the version control information. Do some random changes to your tree now - poke around in a few files or something.

Making Changes

When you've edited some files, next you add them to version control. First you check what you've done:

git diff

svn diff | less

That's it. This is one of the more powerful commands. To get a diff with a specific revision and path do:

git diff revpath

svn diff -rrevpath

There is a more concise representation of changes available:

git status

svn status

This will show the concise changes summary as well as list any files that you haven't either ignored or told Git about. In addition, it will also show at the top which branch you are in.

The status command also shows the "untracked files" that Git doesn't know what to do with. You can handle these by:

git cleangit add files$EDITOR .gitignore

rm -rf filessvn add filessvn propedit svn:ignore .

In fact you need to tell Git when you add, move or remove any files, just like SVN:

git add filegit rm filegit mv file

svn add filesvn rm filesvn mv file

You can also recursively add/remove whole directories and so on; Git's cool!

If you made changes to a file that you want to undo, you can get back the last version you committed:

git checkout path

svn revert path

So, it's about time for us to commit our changes:

git commit -a

svn commit

This looks the same, but there are two important differences:

First, you have to specify -a if you want to commit all your files, instead of just those files you've recently done a git add or git rm on. This can be frustrating at first, but several important techniques are based on it (for example, git add -p lets you add part of a file - see the git-add manual page for details).

Second, Git commits are private by default - they aren't pushed to any central server. We'll talk about pushing changes later, but private commits have some important benefits. For example, when you realise you left some debugging in your last commit, or made a typo in the commit message, you can do git commit --amend to fix it, or even do git reset HEAD^ to toss the commit away completely without affecting your files.

A few words about the commit message: it is customary to have a short commit summary as the first line of the message, because many tools just show the first line of the message. You can specify the commit message using the -m parameter (extra -m arguments will create extra paragraphs in the commit message).

Browsing

Now that we have committed some stuff, you might want to review your history:

git loggit blame file

svn log | lesssvn blame file

The log command is more powerful than in SVN. For example, git log --oneline only shows the first few characters of each commit ID and the first line of each commit message. See the git-log manual page for more stuff git log can do.

The blame command is also more powerful than SVN, as it can track changes even across file copies and renames. But you probably want to do something different! When you used svn blame, you were probably only interested in the history of a few lines of code. git log -p will show the changes to the file in each revision (so you can search for the code you're interested in), and git log -Sstring will show just the commits which add or remove text matching string.

You can see the contents of a file, the listing of a directory or a commit with:

git show rev:path/to/filegit show rev:path/to/directorygit show -s revgit show rev

svn cat urlsvn list urlsvn log -rrevurlsvn diff -crevurl

Branching and Tagging

Subversion marks certain checkpoints in history by copying directories. The copy is usually placed in a directory called tags (if it's a tag) or branches (if it's a branch). Git marks checkpoints in history by applying a label to a commit. For example, the file .git/HEAD is the label for the current commit, and .git/refs/heads/master is the label for the main development branch. Git branches and tags take some getting used to, but are more powerful when you do.

In Subversion, you create a branch by copying your project to a subdirectory. In Git, you tell it, well, to create a branch:

The first command creates a branch, the second command switches your tree to a certain branch. You can pass an extra argument to git branch to base your new branch on a different revision than the latest one.

Running git branch without arguments lists your branches. The * in the output marks the current branch.

git branch

svn list svn://svn.example.com/branches/

To move your tree to some older revision, use:

git checkout revgit checkout branch

svn update -r revsvn update

Git's behaviour when you check an older revision out can be a bit confusing, but also kinda cool. SVN would say you are now on an older revision of the same branch, because you have checked out older files in the same directory. But because a Git branch is just a label for a commit, Git would say you are not on any branch at all. SVN would also say that you can't commit until you update your branch to its rightful place. Git will let you commit as much as you like, although you can get in trouble if you don't know what you're doing.

When you are not on a branch, you are known as having a detached HEAD (i.e. your HEAD commit is not attached to any branch). This is one of the few things in Git that has earnt its scary name - if you make commits on a detached HEAD, then you do git checkout some_branch, what git checkout command would you use to get your commits back? You should almost always do git checkout -b new_branch before you make any commits, so you can get back easily later on. In other words, be careful and don't lose your HEAD.

Git tags are fairly similar to Git branches, but with some extra tricks. Git tags can have a date, commiter, and message that act just like the equivalents for Git commits. They can also be signed with a PGP key if you really want to stamp them with your seal of approval. This is great if you want to release a public version of your work, because you can have one place to store your release announcement and your guarantee that the code hasn't been tampered with. So, let's do it:

Merging

Git supports merging much better than Subversion - both branches have their history preserved over the merges, and you don't have to keep track of which revision you were at last time when you merge again. Unfortunately, you lose much of this power if you use git-svn to share your work with an SVN server.

If you are not using git-svn, make sure you are on one of the to-be-merged branches and merge the other one now:

If changes were made on only one of the branches since the last merge, they are simply replayed on your other branch (a so-called fast-forward merge). If changes were made on both branches, they are merged intelligently (a so-called three-way merge). If the tree-way merge doesn't have any merge conflicts, it makes a commit with a convenient log message (the --no-commit option disables this). If there are merge conflicts, git merge will report them and let you resolve them.

When you have merge conflicts, you usually want to see a diff of just the conflicting changes. git diff doesn't normally show changes that have been marked with git add, so Git automatically adds all the files that didn't conflict, giving you a nice small diff. You can do git diff --cached if you just want to see the non-conflicting changes, or git diff HEAD if you want to see everything.

Like with SVN, you need to tell Git when you have resolved your conflicts:

git add file

svn resolved file

This has the nice side-effect that files disappear from git diff as you resolve them.

Aside from merging, sometimes you want to just pluck one commit out of a different branch. To apply the changes in revision rev and commit them to the current branch use:

git cherry-pick rev

svn merge -c revurl

Going Remote

So far, we haven't talked much about how Git is a distributed version control system. It is time for us to set the record straight.

If you created your repository with one of the clone commands, Git will have already set up a remote repository for you called origin. If you created your repository from scratch, you might want to skip this section and come back to it later.

When you cloned your repository, Git downloaded all the branches and tags in that repository, and created your master branch based on the master branch in that repository (or the trunk branch if you used git svn clone). Even though it only used the master branch, it kept copies of all the others in case you needed them. Copies of branches from a remote repository are called remote branches, and don't behave quite like the local branches you've used before.

For starters, remote branches don't show up in a normal git branch. Instead, you list remote branches with git branch -r. You can log these branches, diff them and merge them, but you can't commit to them, or they would stop being copies of the branch on the remote repository. If you want to work with a remote branch, you need to create a local branch that "tracks" it:

git checkout -t origin/branch

svn switch url

Now, how do you download new changes from a remote repository? You fetch them with git fetch (or git svn fetch). But usually you don't just want to fetch, you also want to merge any changes into your local branch:

git pull

svn update

Again, git-svn does this a bit differently:

git svn rebase

svn update

A pull is technically a bit different to a rebase. As always, see the relevantmanualpages for details.

Sharing your Work

We saw in the previous section that you can pull other people's work into your repository, but how do your changes get back out? Well, your Git repository is as good as any other repository, so you could just ask people to git pull from you the same way you git pulled from them. This is fine as far as Git's concerned, but what if you have a slow Internet connection, or you are behind a firewall, or you like to amend your commits before letting people see them? Most people get around this by having two repositories: a private repository they work on, and a public repository for people to pull from. Some people prefer to share one public repository between the whole team, which is basically the same as having one central SVN repository.

So how do you get your work onto your public repository? Well, it's the opposite of git pull, so you git push! Of course, git-svn is different as usual: git svn dcommit.

Public repositories are generally accessible over SSH or HTTP. SSH is more secure, and it's highly recommended to put your SSH key into an SSH agent (such as ssh-agent or PuTTY's pageant) so you don't have to type it in all the time.

If you need to create your own public repository, you should read the section on public repositories in the Git User's Manual. It's worth emphasising that the manual will get you to make a bare repository - this is effectively just a .git directory with no files checked out. Bare repositories save space and remove the temptation to work directly on repositories that get pushed to (what would you expect to happen if someone pushed changes into a branch while you're working on it?).

Finally, some projects hardly bother with public repositories at all - they just e-mail their commits to each other! In fact development for Git itself is done largely by people sending commits to the mailing list. You can create patches with git format-patch and apply them with git am. To maintain a set of patches, you can use StGIT (see the StGIT Crash Course for details).

What Has Git Ever Done For Us?

We've covered the details of how to work with Git, and how to share your work with others, but is it really worth the hassle? Let's end with an analogy for why a lot of people are happier when they use Git:

Think of your program as a complicated machine sitting on your desk. As you make changes to your machine, you write down each of the steps you take, with each discrete change in a different letter.

With SVN, you dictate each letter to a secretary, who puts it in an envelope and sends it out to everyone. This is very simple, but also quite limiting. You can't just share your letters with one person to proof-read, you have to wait on your secretary for even the most trivial changes, and any mistakes you make are permanently recorded for your shame.

With Git, you write each letter and put it in an envelope on your desk (the index). When you're happy with what you've written, you seal the envelope and put it in your outbox (commit it). Then finally you send your letters off (you push your commits) or invite people to come and pick them up (they pull your commits). This is more complicated, but it's also liberating. Now you can tear open an envelope in your outbox before anyone sees it, or write letters on the train and queue them up to be sent, or pass letters around a small team before making them widely available.

As you get used to Git, and learn how to manage your issues and correct your mistakes, you stop thinking of yourself as "a stupid developer that needs a secretary to look after me", and start thinking of yourself as "a smart developer that demands a personalised
workflow". And who wouldn't be happier like that?