This is part of a series on Rails 2 Upgrade Turbulence.
Now that I had confidence that the code was production-ready, it was time to merge changes back into trunk. We still have a main SVN repository, although most people use the git-svn bridge. This proved problematic in three ways which have nothing to do with Rails whatsoever, but could prove useful to know about.
I use a git-svn repository where the
master branch tracks the trunk
of SVN. I branched from
master to create the
branch. This follows the canonical example:
A---B---C dev_rails2 / D---E---F---G master
I also pushed
dev_rails2 to a remote Git repository. A
few co-workers and I worked in the
dev_rails2 branch and checked in
to the remote repository, and I pulled all changes back into my local
To prevent the gigantic merge that the Rails 2 upgrade would create, I pulled trunk into the topic branch weekly. This involved:
If you do things this way, you have to use a more obscure git-svn command to commit your changes – dcommit won't work. Can you spot the reason from looking at my workflow? No? Well, don't feel too bad; I didn't anticipate the problem either.
If you merge
dev_rails2 as I did, you will eventually
want to merge
dev_rails2 back into master to commit. This can happen
with no conflicts if you resolved everything in
dev_rails2 merges already.
So I'd do this:
But then in order for
git svn dcommit to work, I'd need to do this:
The last step will rewind all of your merged changesets (which now include some of the existing commits in Subversion) and apply them to the current state of subversion. This generates conflicts because it's attempting to diff the already resolved state against Subversion, and the resolution often would not apply cleanly.
The correct solution is:
git svn set-tree [first-hash]..[last-hash]
You should consider using dcommit instead of this command. Commit specified commit or tree objects to SVN. This relies on your imported fetch data being up-to-date. This makes absolutely no attempts to do patching when committing to SVN, it simply overwrites files with those specified in the tree or commit. All merging is assumed to have taken place independently of git svn functions.
set-tree? Yeah, I'd never heard of it either. And I fixed my problem
before I learned about it. In order to save revision history, I decided to
re-resolve all of the conflicts (sometimes badly, since there were too many
commits to remember every bit of minutiae) and fix the bad commits later.
This meant I'd have the following:
master(and therefore Subversion) that I wanted to keep.
dev_rails2that I wanted to keep, but that also contained random, badly-resolved conflicts.
dev_rails2 branch was in a good (i.e. a correctly merged view
of the final product that I wanted to commit) state, I managed to get #3 by
making a separate clone of it and copying files into the checkout in which I
had run (and resolved)
git svn rebase. This allowed me to preserve ~130
commit comments worth of Rails 2 upgrade knowledge. But man, I wish I had
After finally merging in
dev_rails2 and getting a clean
git svn rebase, I
was ready to commit. Not so fast –
git svn dcommit failed rather
[hash] doesn't exist in the repository at /usr/lib/git-core/git-svn [...] Failed to read object [hash] at /usr/lib/git-core/git-svn line [...]
Who knows what
set-tree would have done here. Two of my changes had made it
into Subversion, but the process halted on the commit in which I had copied
the new mysql_replication_adapter plugin into vendor/plugins. After Googling
around, I discovered this great post about submodules
causing this of failure.
Although I didn't have a git submodule, the basic issue turns out to be the same. Something in my commit (I believe it was an empty directory) appeared differently in the subversion changeset. When git-svn attempted to map this back to the git objects it knew about, a mismatch occurred. The object existed in one repository but not the other, so the system (wisely) errored out.
Unfortunately, fixing this was not as simple as deleting the bad directory in HEAD. git-svn has to commit your work sequentially, so I needed to rewrite history.
$ git tag broken [hash that failed to commit] $ git checkout broken $ # remove the directory, re-add with correct name, cross fingers $ git commit --amend $ git rebase --onto HEAD broken master
After rewriting the svn-incompatible changes,
git svn dcommit worked! But of course there was one more issue.
I had to update quite a few plugins, and I did this by checking out the source code and copying it into place in vendor/plugins. This was a short-sighted way of doing things, but using git-svn puts one in the unenviable position of being able to use neither git submodules nor svn:externals. Unfortunately, git-svn is more than happy to let you check full copies of Subversion repositories into your own Subversion repository. Mind blowing, no?
The affect of this is that when you
svn up, Subversion fails to update because
it can't actually add the .svn directories that are in the source tree –
it'd overwrite the ones that it truly needs! Also unfortunate is that you can't
delete them locally because a) they don't exist yet and b) even if they did,
the svn client helpfully ignores any attempts to operate on files named .svn. So
this is broken and will fail:
svn del -m 'Delete .svn checkin' path/to/bad/.svn
Instead, one must delete the checked-in .svn directory on the remote server:
svn del -m 'Delete .svn checkin' svn+ssh://svn-server/trunk/path/to/bad/.svn