Saturday, February 20, 2010

Private Branches in Subversion

For anyone still learning svn, this is what I've been doing when I create and use private branches (aka "feature" branches).

For me, the main motivation for creating a private branch is to stash some code on which I've been working when I have to switch over to something else before I've finished (or can't check it in yet for some other reason). You could also use a private branch to collaborate on (or just share) some experimental feature with another developer, or to submit a bugfix that needs to be reviewed before checking it into the mainline (and of course you can use the same switching/merging techniques with release branches as you do with private branches).

Here are two other tutorials for using branches (although some of Evan Weaver's "best practices," like deleting the trunk of the repository, are probably not actually best practices in practice):

1. Decide where to put the private branch in source control.

Some projects might just use the standard branches directory for this. Other projects have a sibling of the trunk called experimental or exp or just ex specifically for these kind of experimental branches. I'll use exp in this writeup, and I'll put it in a repository named my-project — although it can be anywhere (outside of the trunk or whatever part of the tree from which you're branching, obviously).

So, assuming you already have the my-project repo set up, first create a temporary working copy for exp in a temp directory (checkout with --depth empty to avoid pulling down the whole repo):

$ mkdir /tmp/my-project $ cd /tmp/my-project $ svn checkout —depth empty http://example.com/svn/my-project .

Then create the exp folder, and check it in:

$ mkdir exp $ svn commit -m 'created an experimental section for my-project'

2. Create the private branch.

Ideally, you've already got the working copy of the trunk locally, and updated it to the latest revision. In that case, you can just branch from the head revision of the trunk. You use the svn copy command to do this, specifying the source url (the trunk) and the destination url (the new branch root: my-new-branch). This is completely a server-side operation, so it doesn't matter from where you invoke it. Also, copying is very cheap in subversion, so it should execute quickly (if you ignore the usual network latency):

$ cd ~/projects/my-project $ svn update $ svn copy http://example.com/svn/my-project/trunk http://example.com/svn/my-project/exp/my-new-branch -m 'branch from trunk at 9269'

In practice, your working copy may be a few days out of date; in that case you should branch from the trunk revision to which you last updated (or update your working copy to the head):

$ svn copy -r 8189 http://example.com/svn/my-project/trunk http://example.com/svn/my-project/exp/my-new-branch -m 'branch from trunk at 8189'

3. Switch your working copy to the private branch.

Now that the branch has been created, you can switch the working copy of the tree you currently have checked out from the trunk to the new branch. If you want to switch your entire working copy, just use the switch command from the working copy root, and specify the url of the branch to which to switch:

$ cd ~/projects/my-project $ svn switch http://example.com/svn/my-project/exp/my-new-branch

You can also switch just part of the tree, like just one specific component on which you want to work:

$ cd ~/projects/my-project/foo/bar $ svn switch http://example.com/svn/my-project/exp/my-new-branch/foo/bar

Note that switch is very similar to update — it will update your working copy (at least the files you haven't modified locally) to match the revision of the branch to which you switched. If I just created my-new-branch from the head revision of the trunk, but I haven't updated my working copy in a while, when I switch from the trunk to my-new-branch, it's going to pull down all the files that have been updated since I last sync'd. If your working copy is messy or out of sync, you're probably better off switching just specific projects/components/folders/files within the branch, rather than the whole thing.

4. Commit to the private branch.

You can commit as often as you want to the private branch. Descendants of directories that have been switched automatically are committed to the branch to which their ancestors have been switched. For example, in my local my-project folder, the root of my working copy is pointed to the trunk, but the bar directory is switched to my-second-branch, and a.html is switched to my-first-branch:

projects my-project: ^/my-project/trunk foo bar: ^/my-project/exp/my-second-branch/foo/bar baz a.html: ^/my-project/exp/my-first-branch/foo/bar/baz/a.html b.html

If I commit a.html, it will be committed to my-first-branch; and if I commit b.html, it will be committed to my-second-branch. I haven't actually tried this out, but if try to commit both at the same time (ie by committing from any ancestor folder), I believe each will be committed to the correct branch (a.html to my-first-branch and b.html to my-second-branch) in the same atomic checkin. The same holds for other svn commands, like update, diff, etc: files will be compared to the branch/revision to which you've switched their nearest ancestor.

You can always tell which branch (and revision) to which you have switched a given file with the svn info command. Among other things, it will print out the URL of the corresponding file in source control, and the revision (of the branch) to which you last updated:

$ cd ~/projects/my-project $ svn info
... URL: http://example.com/svn/my-project/trunk ... Revision: 9269 ...
$ svn info foo/bar | grep URL
URL: http://example.com/svn/my-project/exp/my-second-branch/foo/bar

5. Merge updates from the trunk to the private branch.

If you've been working on your branch for a while, you may want to get the latest updates from the trunk to see how they work with your branch (and then continue working on your branch). You use the merge command for this. If you want to merge the entire trunk to your switched branch, it's pretty straightforward. You used to have to specify the revisions from which you originally branched (or the last revision you merged), but the latest version of svn (starting with 1.6?) can figure it out for you automatically — so you only have to specify the url of the branch from which to merge:

$ cd ~/projects/my-project $ svn merge http://example.com/svn/my-project/trunk

You can also use the merge command to merge just specific revisions or specific files from the trunk:

$ svn merge -r 9269:10035 http://example.com/svn/my-project/trunk/foo/bar/baz/a.html foo/bar/baz/a.html

You can always skip this step if you don't care to get the latest from the trunk while you're working — you can always check that your new code works with the trunk when you merge back from the private branch to the trunk (in the next step), anyway.

6. Merge the private branch back into the trunk.

Finally, when you're ready to merge back to the trunk, make sure you've committed everything you're going to commit to the private branch. Then switch your working copy back to the trunk:

$ cd ~/projects/my-project $ svn switch http://example.com/svn/my-project/trunk

If you've switched individual subdirectories in your working copy, you have to switch them back individually:

$ svn switch http://example.com/svn/my-project/trunk/foo/bar foo/bar $ svn switch http://example.com/svn/my-project/trunk/foo/bar/baz/a.html foo/bar/baz/a.html

Now merge in the changes from your private branch:

$ svn merge http://example.com/svn/my-project/exp/my-new-branch

If you've done any merging from the trunk to the private branch (step 5), you should also use the --reintegrate flag so svn knows it can avoid merging back changes from the branch to the trunk that it previously had merged from the trunk to the branch. Finally, commit the merged changes to the trunk:

$ svn commit -m 'merged my-new-branch into trunk'

7. Delete the private branch.

You don't have to delete the branch, but once svn has merged from the trunk to the branch and back again, it may have problems tracking further merges between the two branches. You can continue developing with the private branch if you really want to — you'll just have to be much more careful when you merge. The easiest thing simply is to create a new private branch.

If you do delete the branch and ever need, for some reason, to get back to it, it will of course still be there — you can just switch back to it with the svn switch command (or check it out as a separate working copy), as long as you know the revision range in which it existed (which you can always dig out of the logs):

$ svn delete http://example.com/svn/my-project/exp/my-new-branch $ mkdir ~/projects/my-project-old $ cd ~/projects/my-project-old $ svn checkout -r 10036 http://example.com/svn/my-project/exp/my-new-branch

No comments:

Post a Comment