The integration between git and Subversion (git-svn) is so well done
that several of us have been using git as our interface to all our
Subversion repositories. Doing this is fairly simple, but there are some
interesting tricks, and so I thought I would share a day in the Viget
life with git-svn.
Getting your repository set up
Checking out a Subversion repository is as simple as can be:
git-svn clone -s http://example.com/my_subversion_repo local_dir-s is there to signify that my Subversion repository has a standard layout (trunk/, branches/, and tags/.) If your repository doesn’t have a standard layout, you can leave that off.As you would expect, this leaves you with a git repository under
local_dir.
It should map to the trunk of your Subversion repository, with a few
exceptions. First, any empty directories under Subversion won’t show up
here: git doesn’t track empty directories, as it tracks file contents,
not files themselves. Also, files you were ignoring via svn:ignore are not ignored in this git repository. To ignore them again, run the following command in the root of your repository:git-svn show-ignore > .gitignore
You may see this same command as
git-svn show-ignore >> .git/info/exclude
elsewhere, and that is a valid way to get the same outcome. The
difference is that with the former method, you are adding a file in your
repository that you can then track. Even though we will be committing
back to Subversion, I like tracking .gitignore. As you may have noticed, running git-svn show-ignore is slow, and by committing .gitignore back to the repository, others using git-svn won’t have to run this again.Making branches
One of the best reasons to use git is its lightweight local branches.
These are not the same as Subversion branches: they reside locally and
can be created, destroyed, and merged easily. When working on a project,
you’ll probably want to create a branch every time you work on a new
feature. It’s very simple to do so: run the command
git branch new_branch_name master.
“master” is the branch you are forking off a new branch from. If you
want to fork from the current branch, you don’t have to say so. You can
just as easily type git branch new_branch_name. To move to this new branch, you run git checkout new_branch_name. To make things easier, all of these steps can be combined, like so:
git checkout -b new_branch_name [old_branch_name]
To see all the branches in your repository, you can execute
git branch.
You might wonder why your Subversion branches do not show up. They
exist as remote branches, not local branches, but you can still get to
them. Execute git branch -a to see local and remote branches, which should show you your Subversion branches and tags.Adding changes
Once you are in a branch for your feature, basic git usage is much like Subversion. When you create a new file, you executegit add <new_filename> to start tracking the new file, and git rm <filename> to remove a file. You will note that git add is recursive, and so git add . will add all new files in the current directory or below. git rm will do the same thing, but you have to explicitly give it a -r
flag. Do not make the same mistake I did early on: I manually removed a
few files, and then, extrapolating from my experience with git add ., I thought, “I’ll run git rm -r . to stop tracking those files.” Definitely not my smartest moment.For files that are already being tracked, you will still have to run
git add to add changes to a changeset. You can do this by executing git add <filename> or git add . (to add every change), but I prefer another way:cnixon$ git add --patch
diff --git a/config/environment.rb b/config/environment.rb
index f3a3319..34ece3a 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -19,6 +19,8 @@ Rails::Initializer.run do |config|
# Only load the plugins named here, in the order given. By default, all plugins in endor/plugins are loaded in alphabetical order.
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ config.gem 'chronic'
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{RAILS_ROOT}/extras )
Stage this hunk [y/n/a/d/?]? y
<stdin>:9: trailing whitespace.
warning: 1 line adds whitespace errors.
When you run
git add --patch, git will show you every chunk
- that is, every set of changes - you made, and ask you whether you
want to add it or not. This easily allows you to commit some changes,
but not others, but even more importantly, it begins to show you how git
tracks changes differently. git does not track files, but instead
tracks text and changes to that text. It is a good habit to get into:
looking at each of your changes before a commit helps make sure you are
committing what you think you’re committing. Reverting changes
One command I used to use a lot in Subversion that doesn’t have a clear equivalent in git issvn revert. In git, how you revert depends on whether the change has been added to the current changeset. If it hasn’t, you can execute:
git checkout <filename>
I know, that seems incredibly strange. I thought so, too, at first.
It makes sense when you think of it as re-checking out the last
committed version over your changes.If you have added your change to the changeset, you will have to remove it from the changeset first, by executing:
git reset HEAD <filename>Committing changesets
Committing is very familiar if you are used to Subversion. You executegit commit
and get prompted for a message in your favorite text editor. If you
have outstanding changes, and want to add them without looking at them,
you can execute git commit -a.Merging a branch back to master
Once your feature is finished, you’ll want to merge your branch back to the master branch. This is way easier than it would be in Subversion. You just execute:git checkout master
git merge <feature_branch>
If there are conflicts, you will be prompted to fix them. They aren’t interactive, so you will have to go fix them in your editor. Add the conflict fixes to the changeset with
git add, and then commit.Sometimes, you may make a lot of commits in your feature branch that you want represented as one commit in the main branch. This is especially useful if you’re using CruiseControl or some other automated testing tool: you can make one large commit that passes all the tests, and don’t necessarily have to worry about passing all the tests while developing your feature. To do this, add the
--squash flag to git merge, like so:
git merge --squash <feature_branch>Updating from and committing back to Subversion
Before committing back to Subversion, you will want to update to apply any new changes in the repository to your local Git repo.git-svn rebaseThis will download all new changesets from Subversion, apply them to the
last checkout from Subversion, and then re-apply your local changes on
top of that.When you’re ready to commit back to Subversion, execute: git-svn dcommitThere are a lot more commands and options in git to learn, but these
should be sufficient for most day-to-day usage of git as a front-end to
your Subversion repository. After using it for the last month, I cannot
imagine going back.
