Git

From GnuCash
Revision as of 00:04, 2 June 2011 by Jralls (talk | contribs) (Correct the download URL for git-update)
Jump to: navigation, search

Git and Gnucash

What is Git?

Git is a distributed version control system (VCS) originally developed by Linus Torvalds for managing Linux source code without requiring a central server. It is also the primary VCS used by the Gnome and Free Desktop projects. You can get the latest version for your system and read a rich variety of online documentation at Git's Home.

What has that to do with Gnucash?

Some of Gnucash's primary developers are experimenting with using Git to take advantage of its branching and merging facilities, which are much richer than those provided by Subversion. We have an experimental set of repositories at Github for the code, documentation, and the website, which are updated from the canonical subversion repository every 4 hours.

Using the Github Repository

Non-Committers

Just clone the repository as usual:

 git clone git://github.com/Gnucash/gnucash.git

Note that the default branch is trunk, not master which is normal for git. If that bothers you, just make a tracking branch named master:

 git branch -t master refs/remotes/origin/trunk

When you have patches, use

 git format-patch origin/trunk..master

(or git diff) in the root directory of your local repository to prepare them; then add the patchfile as an attachment to the appropriate bug report.

If you have a Github account, it turns out that Github's "fork" feature doesn't play well with the Gnucash repository because of its unusual structure (which in turn is needed to synch it with subversion).

Instead, create a repository in your account (you can name it whatever you like, but calling it Gnucash is likely to minimize confusion), then clone the Gnucash/gnucash repository on your local computer. Add your Github Gnucash repo as a remote

 git remote add github git@github.com:myname/gnucash.git

and then you can push to it as usual

 git push github trunk

Committers

Set up

Committers start by cloning the repository the same way. Since changes need to be tagged with the subversion revision, no-one should push to the Git repository; a good way to make sure that this doesn't happen by mistake is to use the same read-only URI given above for non-committers. Alternatively, fork the Gnucash repository to your Github account and clone that (use the read-write URI in that case).

Next download git-update, a shell script to pull changes from github and fixup the branch references for git svn. Put it somewhere on your path. Edit it so that the path to the git library directory (5th line) is correct for your installation or set $GITPERLLIB in your environment to point to the location of Git.pm.

Change directory to your new local repository and run

 git svn init --stdlayout svn+ssh://YOURNAME@svn.gnucash.org/repo/gnucash
 git-update

Note Be sure to substitute your svn.gnucash.org userid for "YOURNAME" in that URI!

That's it. Always use git-update instead of git pull. If you forget, git svn will error out because the refs that git svn can see won't match the ones in refs/remotes/origin. It's no big deal, though, just run git-update and everything will be fixed up.

Note git svn doesn't always provide helpful error messages; it will often just fail with a perl error. Aside from messed-up references, it doesn't handle gracefully failing to connect to the svn server.

Committing

 git svn dcommit

Will commit your changes back to the subversion repository.

Branching and Merging

Git and Subversion treat merges very differently. When Git merges, it creates a new reference with more than one parent. It knows when assembling a working copy or displaying a log how to follow those multiple parents and apply their changes in order to produce the target version. A rebase on the other hand patches the target branch for each revision in the supplying branch, creating a new set of references. Cherry-pick does the same as rebase except that it only applies selected revisions.

Subversion doesn't understand this "parent" thing. It merges by making a single patch containing all of the differences between the two branches and applying it to the target branch. The history of how those differences evolved is lost, and there's not even an indication that the changes came from another branch unless the committer mentions it in the commit message.

When you run git svn dcommit, Git svn must effectively rebase your git changes onto the subversion repository because, well, they're two separate repositories and there can't be any references between the two. If the git history that git svn dcommit is committing to subversion contains merges, the revisions on the different merged branches will be flattened into a single chain, because that's what subversion can understand.

The problem, of course, is that the subversion history will now look very different from the git history. The subversion view of the history with all of the changes on the target branch will be propagated to the git mirror. When you next update your local repo from the git mirror, the revisions in the target trunk will be added, making a mess of your history.

To avoid this problem, don't use git merge to branches that get dcommitted back to subversion; use git rebase or git cherry-pick instead. That way the history in your local repository will match what is put on subversion, and what comes back to you from the git mirror. Skillful use of git rebase offers substantial control over what revisions appear in subversion -- but you must do the work in your local Git repo before calling git svn dcommit.

It's worth noting here that git svn rebase is quite different from git rebase: The former is the git svn analog of git pull --rebase. That is, it takes all of your local commits and sets them aside, updates your local repository from subversion (creating git commits from each subversion revision, of course) and then replays your commits on top of that. Git svn dcommit does that automatically to ensure that it doesn't overwrite other revisions when it commits yours.

One more thing: It's normal Git practice to make a branch for anything that will take more than one commit to accomplish -- and in Git it's normal to commit small changes often so that you have fine granularity when you change your mind about something, so most git users have lots of branches in their repositories. There's no need to share the vast majority of those branches, and since branching in subversion is expensive, please don't commit your feature branches back into subversion.

To make this more clear, here is an example of a workflow:

git checkout trunk
git-update

This makes sure you start from the branch that is synchronised with svn

git checkout -b feature

Create a feature branch to do your work on. While working you create several commits on the branch. When you are ready to push this to subversion do:

git checkout trunk
git-update
git rebase trunk feature

to synchronize trunk with the master git repository again and base your feature changes on the most up to date trunk branch

git svn dcommit

to send your commits upstream to svn. Now wait until after the next update (every 4 hours) before you run

git-update
git checkout feature
git merge trunk

And you are ready to continue your work.

Collaboration

So Subversion can't see our Git branches. What do we do if several developers need to work together on a feature?

There are several ways to go about it: You can pass patches between you over email, chat, or carrier pigeon; Git is designed to handle that easily (except for carrier pigeon transport, as that requires retyping the patch, which is a pain). You can arrange for all of your repositories to be available on the net, and git pull amongst yourselves. Or you can use one of the public repositories like Github and Gitorious to manage your changes.

Setting up and Maintaining the Mirror

This is mostly for documentation in the event that for some reason someone else needs to take over updating from subversion, or someone from another project happens upon this and wants to do the same themselves.

Download git-svn-mirror and gnc_authorsput it on your path.

If you're taking over updating, set up a committer's clone (see above) to get started from. Adjust the config file to look like this:

 [core]
   repositoryformatversion = 0
   filemode = true
   bare = false
   logallrefupdates = true
   ignorecase = true
 [svn-remote "svn"]
   url = svn+ssh://you@svn.gnucash.org/repo
   fetch = gnucash/trunk:refs/remotes/svn/trunk
   branches = gnucash/branches/*:refs/remotes/svn/*
   tags = gnucash/tags/*:refs/remotes/svn/tags/*
 [svn2git]
   prefix = svn/
 [svn]
   authorsfile = /home/gnc_authors.git
 [remote "origin"]
   url = git@github.com:Gnucash/gnucash.git
   fetch = +refs/remotes/svn/*:refs/remotes/origin/*
   push = refs/remotes/svn/*:refs/heads/*

Note the "you" in the svn URI and the authorsfile path.

Now you can run

 git-svn-mirror update /path/to/git-repo

to test it. If everything works, you can add it to a cron job and you're done.

If you're starting from scratch, there's a little prep work, mostly involving setting up the authors file. Instructions are in the git svn documentation; git-svn-mirror is a perl program and is documented with "Plain Old Documentation"; you can read it with git-svn-mirror man. The important thing to remember is that you should run git-svn-mirror clone only once; even if it doesn't completely succeed, always run git-svn-mirror update on an existing mirror.

Another note is that large subversion repositories take a long time to fetch; Gnucash's took 10 solid days on a 2.8GHz 4-core Mac Pro when there were 20,000+ revisions.

Acknowledgments

The workflow was designed by Thomas Ferris Nicolaisen. More information and a nice illustration can be found on his blog.

As noted in the documentation, git-svn-mirror is based on a Ruby tool called svn2git.