Thursday, January 25, 2007

openCollabNet

Just a quick post to point you to a site that you might not know about: openCollabNet. This is a site being hosted and supported by CollabNet to provide an online community for Subversion and CollabNet users.

It is a pretty cool site that seems to have a lot of information contained within it such as articles, forums, podcasts etc. I will probably start compiling a list of interesting links and add them to a new post in the future, but for now I would suggest you poke around the various parts of the site and you will likely find something useful.

One of the more immediate things you might find interesting on this site is that they are providing binaries for various Subversion components and tools (such as TortoiseSVN and Subclipse) and I believe they will provide support for those binaries. Currently, I think they are only offering Windows binaries. I would really like to see them provide some for OS X as there currently are not any good sources for up to date binaries for OS X. Metissian used to provide a very nice package, but they seem to have stopped providing them. Not many user's are going to want to download and install XCode so that they can build Subversion from source, and many that do seem to run into problems with various parts of the process -- at least those that are building JavaHL. Anyway, hopefully they have OS X support on their to-do list. Linux binaries would also be nice, but at the same time I think the Linux distributions are doing a decent job providing Subversion packages that work properly, so it is slightly less urgent when compared to OS X.

Tuesday, January 23, 2007

How to Undo a Commit in Subversion

A frequent question that is asked on the Subversion and Subclipse mailing lists is how to undo a change once it has been committed. A variant of the same request is how to get back a file that has been deleted.

If you need to undo a change completely, as in remove all traces it ever happened, there is really only one way to do it. You need to dump the repository up to the revision you want to remove, and then recreate and load the repository from the dump file. I will not be covering that option in any detail in this article. This article will just be about reversing the effects of a commit, as in restoring a deleted file/folder or some change to a file or set of files. Depending on the circumstances, there might be several ways you can accomplish this. For example, the svn copy command can be used to restore something that is deleted. The technique I am going to focus on in this post is called a "reverse merge". Essentially, you use the svn merge command with the revisions flipped around so that the merge command undoes the changes in the selected revisions. For command-line users, the process to do this is explained well in the Subversion book. See this section on undoing changes.

When using a GUI tool like Subclipse, you could just use the Merge option and fill out the dialog using similar values to what you would do from the command line. However, Subclipse offers a built-in technique that makes it very easy to undo a change.

To give proper credit where it is due, the technique that is described in this article first appeared in the TortoiseSVN product. The Subclipse feature described below is based on the TortoiseSVN feature.

Open the History View
The first step is to show the history of the item containing the change you want to restore. If you just want to restore the changes to one file, then select just that file. Otherwise, you can select your project or a folder so that you can undo changes to multiple files.

If you want to restore something that is deleted, then you need to show the history of the item's parent, so that the history will show you the deletion.

Select the Revision(s)
Now, just select the revision, or contiguous range of revisions, that contain the changes you want to undo. Right-click and choose the option to revert the changes from those revisions:




Review and Commit
When the option runs, it will invoke the merge command, supplying the proper parameters to do a reverse merge. Review the results of the merge and commit the changes to finish the process.

Partial Undo?
It would not be uncommon to only want to reverse part of the changes from a commit or series of commits. There are a couple of ways to deal with this:
  1. Select the correct context. If you only want to undo the changes to files in a specific folder, then select that folder when you show the history. Then the merge will only undo the changes in the context of that folder and other changes in the commit will be skipped by the merge process.
  2. Revert what you do not want. Suppose you delete five files from the same folder and want to restore one of them. You have to select the parent folder when you take the option, and this will restore all five files. When the command completes, just use the Revert option to undo any changes you do not want, and then commit the rest. The merge process only restores the change in your working copy. Nothing is forcing you to commit the results of the merge without editing it first.
Conclusion
Undoing the effects of a change in Subversion is a fairly easy process once you understand what it is that you have to do. The Subclipse and TortoiseSVN UI further simplifies the process and saves you from having to lookup and enter the revisions correctly in the merge dialog.

Friday, January 19, 2007

Enhanced Support for Branches and Tags in Subclipse

This post largely draws on content I previously wrote and posted on the Subclipse web site back in December 2005 when this feature was added to Subclipse. I have attempted to freshen the content a bit in some places and repost it here for a new audience that may not be aware of this feature.

The issue of tags in Subversion, and how they differ from CVS, is a frequent topic on the Subversion users@ mailing list. The same issue came up a lot on the Subclipse mailing lists, especially since the CVS plug-in in Eclipse handles branches and tags very nicely. The design for this feature evolved over time out of requests and suggestions from Subclipse users, along with a few niceties that I added while developing the feature when I realized I could use the information in additional ways. I was initially against adding the feature, as I generally try to stay true to the features of Subversion and do not like inventing concepts that are not really supported and backed by Subversion. However, in the end I really like the way this feature turned out, and I find it very useful in my own projects. There are some areas it could still be improved, but all in all, it is a nice feature.

In no way would I attempt to make the claim that this answers every request/idea/complaint that Subversion users have had about the way tags are handled. That being said, it does address a lot of them :)

Overview
In many ways, the support for branching and tagging in Subversion represents a vast improvement over CVS. It certainly addresses many of the complaints with this feature in CVS. There is one significant drawback in the implementation in Subversion. Namely, the process of creating a branch/tag does not leave behind any "breadcrumbs" in the source of the branch/tag operation to indicate that the branch/tag was created. In other words, there is information in the branch/tag that indicates where it came from, but there is no similar information in the origin of the branch/tag that would let you see or know that the branch tag exists. The net result is that when looking at a file or folder there is no easy way to know which revisions belong to which tags.

Subclipse provides a way for you to work around this problem by allowing you to maintain a Subversion property in your project that indicates the branches/tags that have been created from that project. The name of the property is subclipse:tags. The format of the property value is:
revision_number,tag_name,relative_path,branch/tag



The above screen shot shows what the property looks like if you were to edit it directly. However, Subclipse also provides a Configure Branches/Tags option on the Team menu that allows you to edit the property using a custom editor.



Besides providing basic CRUD capabilities, the dialog also includes a built-in repository browser. This allows you to select one or more folders and add them as a branch/tag in one action. When folders are added in this manner, Subclipse automatically fills in the Revision number based on the Last Changed Revision of the selected folder.



In addition to the Configures Branches and Tags option, there is also support for automatically updating the subclipse:tags property when you create a Branch/Tag using Subclipse. When you take this option, if we detect the subclipse:tags property on the item you selected, then we pop-up a dialog that lets you confirm that you want to add this new information to the subclipse:tags property. You then just have to commit that property change after creating the Branch/Tag. This feature does not exist when creating the Branch/Tag from the repository browser as it is only possible to modify a property value in a working copy.

A nice aspect of using a Subversion property to drive this feature is that only a single user or build process needs to assume the responsibility of managing the property value. They just commit the property change and all users receive the benefits in their UI when they update.

Features
Once the property has been defined, there are a number of ways that Subclipse takes advantage of it to provider a better user experience in the UI.

History Browsing
This was really the number one reason for pursuing this feature in the first place. The History View has been enhanced to show the tags for a revision.



When the History option is invoked against a local resource, we read the subclipse:tags property value from the local working copy and use its contents to populate the tags column in the view. A preference controls whether to show this information when browsing history directly from the repository. In this scenario, in order to show the tag information, we have to search the repository for the presence of the subclipse:tags property. On a slow connection you probably would not want to do this.

Compare Revisions
The Compare with Revision ... option has been enhanced to show tags in its history view. This makes it easy to know which revisions you want to compare.




URL Chooser
The URL Chooser that allows you to pick a URL in many of the Subclipse dialogs has been enhanced to show Branch/Tag information. This allows you to just select a branch/tag by it's name or identifier and the proper URL to that branch/tag will be automatically created. The enhancements we were able to make to this dialog turned out to be the best "side-effect" of adding this feature. Not having to type out, or browse to select, really deep URL's in the repository is a real time saver and usability improvement.



Compare with Branch/Tag
Technically this feature is not dependent on the property, but it was added at the same time and the property makes it easier to use. Subclipse provides a Compare with Branch/Tag option in the Eclipse Compare With menu. The option gives you the choice of producing a unified diff file or doing a graphical compare as shown in the screen shot below. The Eclipse graphical compare option can be fairly slow if you do not have a fast repository connection. Also, the Eclipse compare option will show differences in svn keywords which the unified diff option will not. That being said it is still convenient to have and combined with the enhanced URL Chooser, it is a very nice option.



In the future, I would like to enhance the graphical compare option to either be driven by a unified diff file (which would be much faster and accurate) or perhaps a new routine that used the new svn diff --summarize option to produce the initial list of differences, instead of letting Eclipse calculate them. Either option would make it run a lot faster and eliminate the false positives caused by the use of keywords.

Future Enhancements
Eugene Kuleshov has requested some enhancements to this feature that might come in the future.

The first is issue 471, which is to remove the revision number from the property value and instead just discover and cache it when needed in the UI. The reason he wants to do this is so that we can change the way we update the subclipse:tags property so that we do it before we create the branch/tag. Then the updated property information would be included in the new branch/tag. See this mailing list thread for his reasons.

I do have some concerns that this will unexpectedly slow down the UI when the cache is populated against a slow server. I suppose that could be solved with proper usage of a progress dialog, but I have the feeling this would happen so deep in the code that might be more difficult that it sounds. What I would ideally like to see is an ability to update the property in the branch/tag as part of creating it, but this would require new Subversion API support.

Eugene's next request was issue 472. This request is to enhance the create branch/tag process to allow you to process several projects at once. I would really like to see Subclipse focus on this area once the 1.2.0 release is finalized. Basically, we need to make it easy to perform all of the branch/tag options on multiple projects. This would include, creating the branch/tag, but also switch, merge and probably compare. This is also entered in issue 596.

Conclusion
This is a nice feature that improves the user experience for Subversion users. There is always more to do, such as better multi-project support, but we will get there.

The one negative I have found about this feature is that your branching policy will sometimes make it less effective. For example, the Subversion and Subclipse projects both have a policy where we create a branch, such as 1.2.x, to stabilize a release. All release tags are therefore made from the release branch, which means the subclipse:tags property, containing the tag information, lives on the release branch. However, developers mostly work in trunk and thus cannot really benefit from the History view feature of showing the tags in the view. This is an area where I think native Subversion support for knowing what tags a revision was copied to would be needed. If Subversion ever added this feature, it could likely tell that a file that was copied to a branch and then tagged is the same file that is on trunk. I guess the problem is whether Subversion can ever add a feature like this and still support the concept of "cheap copies" which is the foundation of the whole product.

Tuesday, January 16, 2007

Features of the Subclipse Commit Dialog

This is a post I have been wanting to write since I started my blog. In this post, I am going to cover some features that exist in the Commit dialog within Subclipse. It is very likely that you will not know at least about one of these features.

Might as well start with a screen shot that shows the dialog and some of these features:



Integration with Issue Tracking Systems
I decided this topic was too big for this post, so I first wrote up a post on how to Integrate Subversion with Your Issue Tracking System. Refer to that post for the details.

Message Width Marker
Look closely at the right hand side of the commit message. See the feint line drawn through the edit control? That lets me see where a specified column of text resides so that I can format messages according to whatever guidelines might exist for the project I am working on. The Subversion commit message is just a blob of text that is stored the way you enter it, including (or not) any line feeds. A lot of people might have routines that parse or reuse the messages and often it is desired that the messages fit nicely within someone's terminal window.

This feature first appeared in TortoiseSVN, and we added the same feature in Subclipse. To turn it on, you need to define an SVN property named "tsvn:logwidthmarker" with a numeric value that indicates the column where you want the line to appear, such as "79". Since most Eclipse users will be checking out Eclipse projects, you should only need to set this property on the project root folder. Otherwise, just set it on all folders. This rule applies to all of the subsequent features I will describe that involve SVN properties.

Handling Unversioned/Missing Files
The commit dialog will show any unversioned or missing files that appear beneath the folder you selected when you took the option. By default, unversioned files are not selected in the dialog, but you can change this in the Eclipse preferences under Team -> SVN. Missing files are never selected automatically. If you select these files, when you click OK, Subclipse will execute the svn add/delete command prior to running the svn commit command so that the items are properly included in the commit process.

Remember Previous Messages
There is a combo box in the middle of the dialog that shows previous commit messages you entered, including the text you entered before you hit the Cancel button. This can be useful if you write the commit message and then suddenly realize you left some debug code in that you want to remove before committing.

Message Templates
Subclipse supports two kinds of message templates. The first one comes from TortoiseSVN, and that is to set an SVN property named "tsvn:logtemplate" where the value of the property contains the template text. If this property is set, the dialog will come up with the text of the template already filled in. The second template system is an Eclipse feature first introduced by the CVS plug-in. This features lets you setup any number of templates in the Eclipse preferences. These templates are all then available in the drop-down combo that shows previous messages that were entered.

Require a Message
This feature also comes from TortoiseSVN. You can set an SVN property named "tsvn:logminsize" where the value is a number. When this property is set, the OK button will not be enabled on the commit dialog until the number of characters entered in the message is equal to or greater than the value of this property. So you can set this to a value like "10" and that would require the user to enter at least 10 characters. If for no other reason, entering a value of at least "1" can save a user from accidentally clicking OK without entering a message.

Internal Resizing
All of the sections of the dialog can be resized. So if you like a large area to enter comments, with just a couple of files showing, you can arrange the dialog that way. Likewise the opposite is true if you would prefer to be able to see more files.

Show Differences
This one is my favorite, and one that a lot of user's do not seem to know exists. You can double-click on any file in the list and see the differences in that file. This can really help in writing proper commit messages.




The only negative to this compare feature is that the dialogs are all modal so you cannot look at the compare while you are typing the commit message. I'd like to enhance the commit dialog someday so that the compare results showed in a new section of the dialog, but I am not sure if that would work well from a screen real estate point of view. There is also the question as to whether the Eclipse compare UI can be embedded in a dialog like that. I suspect it can.

Conclusion
The commit dialog in Subclipse contains a number of features that are designed to make the process of working with Subversion easier and more usable for you. Hopefully there were one or two features described in this post that interest you and that you did not already know about. Most of these features were first developed and included in TortoiseSVN, so I would like to just conclude this post with a thank you to the TortoiseSVN developers for paving the way. Thank you.

Monday, January 15, 2007

Integrating Subversion with your Issue Tracking System

This article will explore a technique for integrating Subversion development with your issue tracking system. This technique was cooperatively developed by the TortoiseSVN and Subclipse development teams, and both products support the features that will be described.

Background
Many popular issue tracking systems have developed procedures for integrating with version control tools like CVS and Subversion. A common approach that is used is for the issue tracker to provide one or more Subversion hook scripts that interrogate the message provided in the commit process, looking for references to issues in the message text. Depending on the tool being used, there will often be some meta data stored within the issue tracker to record the files that were modified for that issue. Some tools support features that change the state of the issue, others might perform some kind of validation and possibly reject the commit transaction. This part is really up to the issue tracker and the hook script(s) they provide.

The problem with this approach is that it only works as well as the ability of the issue tracker's hook script to correctly identify the references to issues in the commit message text. With no help being provided by the version control tool to help the user consistently format their message, this can be a difficult task to perform accurately. Also, even when everything works correctly, the integration is only in one place, the issue tracker. In other words, you can view an issue in the issue tracker and see what files were modified (or some other reference to the version control tool), but when looking at history in the version control tool, you do not have easy access to the related issues that were addressed by the commit.

With all this in mind, these were the goals of the issue tracking specification that we developed:
  1. Provide a place in the UI of the commit dialog for the user to supply issue ID's. These issues are then inserted into the commit message in a consistent manner.



  2. Provide for the possibility of some simple validations in the UI. Such as verifying that the issue is a number, or warning if no issue is entered.


  3. When showing Subversion history in the UI, convert any references to issues in the commit message into clickable hyperlinks to the issue tracker.

You can view the full specification in the TortoiseSVN Subversion repository here. (username = guest with no password)

These features combine to give the user a better experience in terms of referencing their issue tracker from their Subversion client and also makes it much easier to write server side hook scripts that can accurately parse the commit messages. A nice aspect of this feature is that since the integration between Subversion and your issue tracker is still ultimately based on the text of the commit message, even users that are using a client that does not specifically support this feature can still participate in the integration by entering their commit messages in a format consistent with what this feature would have done for them.

Details
With all this in mind, how do you turn on this feature and/or configure it? The way that we settled on was to use Subversion properties to hold the configuration information. This allows one person to set it all up, and the configuration can then be pushed out to other users via the update and/or checkout process. In addition, the configuration just needs to be set on the top-level folder in the working copy. At runtime, the client will walk the working copy back to the root looking for the configuration. This means if you just set it on the root folder, that is enough. Keep in mind, that the client is walking the "working copy" not the "repository". If you have an environment where someone might conceivably check out at any folder level in the repository, then you might need to store this configuration as properties on every folder. Subclipse users are working in Eclipse, and Eclipse has a fairly rigid project structure. So for Eclipse users, it is pretty easy to configure as you just need to put the configuration on the project folder and you can be fairly confident that is what your users are checking out.

Once that is decided, there are then several properties all prefixed with "bugtraq" which determines the configuration of the feature. We chose bugtraq with the slightly odd spelling just to avoid any potential current or future namespace conflicts. Here are the properties and an explanation of each, this is actually copied from the spec verbatim:

NOTE: The specification has evolved a bit since it was first written and now breaks the feature down by a "basic" and "advanced" configuration where the latter is based on the use of regular expressions. Subclipse currently only supports the basic configuration options, and that is also all that this article covers.

Bugtraq Properties:

name : bugtraq:url
value : (string)

This value is the URL pointing to the bug tracking tool. The URL string should contain the substring "%BUGID%" which the client replaces with the issue number. That way the resulting URL will point directly to the correct issue.

NOTE: The URL must be properly URI encoded by the user. This URL can be used by clients to create a link to the bug tracking tool when showing the log message of a revision.

name : bugtraq:warnifnoissue
value : "true"/"yes" or "false"/"no"
If set to "true", then the clients will warn the user if the issue text box is left empty. But it must not block the commit, only give the user a chance to enter an issue number in case (s)he forgot it.

name : bugtraq:label
value : (string)
This can be used by a client as a GUI label describing the text box where the user has to enter the issue number. If this is not set, then a default value should be used, e.g. "Bug-ID / Issue-Number :". Keep in mind though that most GUI clients don't resize their windows according to the size of GUI elements. So keep the size of the label string below 20-25 chars.

name : bugtraq:message
value : (string)
If this property is set, then the client should show a text box in the commit window where the user can enter an issue number. The value string of this property is used by the client to create an additional line added to the log message. The string must contain the substring "%BUGID%"
which is replaced with the issue number the user entered before applied to the log message. The client will add the resulting string as a new line to the end of the log message the user entered:
logmessage + "\n" + resultstring

In case bugtraq:append is set to "false", then the log message is defined as resultstring + "\n" + logmessage

The client should make sure that the string doesn't have multiple lines. If more than one issue number is entered by the user (separated by commas), the client must make sure that there's no whitespace chars before and after the comma. And also the whole issue number string must be trimmed.

name : bugtraq:number
value : "true" or "false"

If this property is set to "false", then the client allows any character entered in the issue text box. Any other value or if the property is missing then only numbers are allowed as the issue number. One exception is the ',' char, which is used to separate multiple issues. The client must never complain if the text box is left empty, i.e. if no issue number is given. Not all commits are assigned to an issue!

name : bugtraq:append
value : "true" or "false"
If set to "false", then the bugtraq:message part is inserted at the top of the log message, if "yes" or not set, then it's appended to the log message at the bottom.

Since these are all Subversion properties, you need to use the svn propset command to set them on a folder within your working copy (there is no way to do it directly against the repository).
svn propset bugtraq:append false .
property 'bugtraq:append' set on '.'
You then need to commit those changes so that other users can get them. Keep in mind that in order to commit property changes to a folder, the revision of the folder in your working copy has to be the same as the HEAD revision of the folder in the repository.

Here is an example of the properties set on one of the Subclipse projects as an example:


If you refer to the previous screen shots, you can get an idea how these properties manifested in the UI of the commit dialog and the text that was inserted into the commit message.

Conclusion
This is a feature in Subclipse and TortoiseSVN that our users lobbied hard for and seem to love. I personally enjoy the easy access to issues that is created by the hyperlinks in commit messages when viewing history. Often reading the history contained within an issue is the best way to understand the context of why a particular change was made.

I do not know the complete list of Subversion clients that have added support for these properties, but I know that it goes beyond Subclipse and TortoiseSVN and includes several of the web-based repository browsers.

The Subclipse integration with Mylar includes partial support for this feature as well. When browsing history within Subclipse, the Mylar "Open Corresponding Task" option will be present and will determine the issue based on these properties and the content of the commit message. I assume this would mean if you were using Bugzilla or another issue tracker with a rich editor in Mylar that the rich editor would be opened in place of the web browser. Unfortunately the Mylar Change Set integration in Subclipse does not currently have a way to plug your issue number into the right part of the commit dialog. It does stick the URL of the issue in the commit message text, so all is not lost. Perhaps in the future it will be possible to enhance this so that the issue ID itself is populated.

Saturday, January 13, 2007

Subversion 1.5: Tolerate obstructions during checkout/update/switch

This is my second article about new features in Subversion's trunk, features that will be included in Subversion 1.5. My previous article discussed enhancements in Subversion's move/copy commands. This article will discuss improvements that have been made to the checkout, update and switch commands -- specifically, the ability to tolerate obstructions.

Prior to these changes, if Subversion needed to add a file or folder to your working copy as part of the checkout/update/switch commands, and a file or folder with the same name already existed, then the command would abort with an error. The message you receive will vary based on the command and the situation, but it will look something like this:
svn: Failed to add file 'foo.c': object of the same name already exists
As of Subversion 1.5, you can add the --force switch to any of these commands to override this behavior and tell Subversion to tolerate the obstruction. The output you normally see from these commands will tell you when this happens. For example, it will look something like this:
E foo.c
A bar.c
A new code of 'E' for 'E'xists has been added to inform you of this situation. What Subversion does in this situation is create the ".svn" meta data, as it normally would. The "pristine" copy of the file is stored based on the repository, as it always would be. The file itself, however, remains unchanged. If the file does not match the file in the repository, then svn status will show the file as containing local modifications. Those modifications can be viewed with svn diff, or committed with svn commit. Likewise, you can use svn revert to restore the local file to the pristine copy.

These changes help with a number of scenarios that can happen in normal development. The original use case that we wanted to solve was a developer that downloaded a tarball of some product and winds up making some fixes. Since they started with a tarball, they do not have a Subversion working copy on their disk. If they want to commit their fixes, or even submit them as a patch, then they need to have a working copy. With this change, they could use the svn checkout command, and the --force switch, to checkout the code from the repository on top of their local copy. When the command completes, they will have a valid working copy. They can now create a patch with their changes, or commit them if they have access.

With the code that is currently in trunk, in this scenario, the svn checkout command will still need to pull down the same number of bytes from the server as a "normal" checkout. It is possible that it could be enhanced in the future to more selectively pull down just the differences, something like an rsync. A change of that nature has been deferred for some future release or whenever someone comes along with enough motivation to tackle it.

Here is another scenario where this change solves a problem. Suppose a developer has a project checked out. They create a patch to implement some feature or fix and that patch includes some new files. They submit the patch to the community and a developer reviews and commits it. When the original developer runs the update command, the command will fail unless they remembered to remove the new files they had submitted in their patch. Now, they can just use the --force option with the svn update command, and it will complete normally and just reconcile any issues. If the patch was committed exactly as it was submitted, the developers working copy should not show any modifications after the command completes. If the patch was tweaked, then a diff of the working copy would show whatever tweaks were made, and the svn revert command could be run to get the working copy back to a pristine state, ready for the next patch to be worked on.

We will be making use of this new feature in several places in Subclipse, including several where we have had to code our own workarounds to get around this problem. There are also a couple of new checkout and project share scenarios where we should be able to utilize this new option to implement features that our users have asked for.

Sunday, January 7, 2007

Shelves in Subversion

There is a popular feature called "shelves" that was included in Microsoft's Visual Studio Team System. I am fairly certain that VSTS is not the first or only tool to have this feature and in this article I will show you how to get the same feature from Subversion. I think of shelves as essentially creating and using a branch to save some changes you have been working on, but now need to set aside, or shelve, for a while. Here is how Microsoft explains the feature in their documentation, which I found via a search:
Shelve your pending changes when you are not ready to or cannot check in a set of pending changes. There are primarily five shelve scenarios:
  • Interrupt When you have pending changes that are not ready for check in but you need to work on a different task, you can shelve your pending changes to set them aside.

  • Integration When you have pending changes that are not ready for check in but you need to share them with another team member, you can shelve your pending changes and ask your team member to unshelve them.

  • Review When you have pending changes that are ready for check-in and have to be code-reviewed, you can shelve your changes and inform the code reviewer of the shelveset.

  • Backup When you have work in progress that you want to back up, but are not ready to check in, you can shelve your changes to have them preserved on the Team Foundation server.

  • Handoff When you have work in progress that is to be completed by another team member, you can shelve your changes to make a handoff easier.

The branching model in Subversion, with its use of "cheap copies" is well suited to provide similar capabilities and handle all five of these scenarios. In the rest of this article, I will detail two different ways to shelve changes using branches in Subversion.

Quick Method: Create Branch from Working Copy

The first and easiest method is to simply create a branch from your working copy. A lot of Subversion users do not realize that you can do this. I actually use this technique a lot when creating complex tags. Just get your working copy exactly as you want the tag, and then create the tag based on your working copy.

svn copy . url://server/repos/project/tags/tagname -m "Create tagname"
This creates the tag in the repository based on what is present in the working copy. Subversion does not even need to send any file contents to the repository to create the tag, so it still runs very fast.

You can use this same concept to create a branch or shelf with the changes you are working on. Here is a screen shot from Subclipse showing how to do this:



Notice that the option to create the copy from the Working Copy has been selected. That is the key to this method. When the command runs, Subversion creates a copy in the repository based on the base revision of your working copy, however any local changes are also committed as updates. Likewise, if you have svn added or deleted any files/folders, then those are also included and it does this all in a single transaction.

After the command completes, your working copy will still appear to be modified (because the changes were committed to a different branch then the one associated with the working copy). To get your working copy back to a pristine state, before beginning your next task, you will need to run the svn revert command to remove all local modifications. The svn revert command does not remove files and folders that were added, so you need to manually delete those after the revert.

Hint: The Subclipse revert process will remove selected unversioned files and folders. So if you run the revert option twice, the first time you run it, added files are made unversioned and the second time you run it, they are deleted.

Creating a shelf using this technique is very easy. The problem with this technique comes when you want to work on your shelf again. To do so, you want to use the merge command to merge your changes from the shelf back into the current working copy which is associated with trunk or some other branch. The problem is that since the shelf was created in a single transaction, merging it is a little bit tricky. Suppose the shelve was created with revision 50. The normal way you might expect to merge it into your working copy would be to run a command like this:
svn merge -r49:50 url://server/repos/project1/branches/shelf
This tells Subversion to merge the changes that happened with revision 50 into the current working copy. However, in this scenario, when you run this command you will get an error something like this:
svn: Unable to find repository location for 'url://server/repos/project1/branches/shelf' in revision 49
The problem is that the shelf did not exist in revision 49, so the command errors out. The answer to this is that you have to run the merge using two URL's. The source URL would be the base location that your working copy was pointing to (which was the base of the copy) and the target URL would be the shelf. You also need to know what revision your working copy was at when you made the copy. You can determine this information by running the svn log command on the URL of the shelf. The output of the command will show both the URL and revision it was copied from. Unfortunately, this is also where it can get really complicated. There is a better than likely chance that your working copy contains mixed revisions (see my previous post on Mixed Revision Working Copies). Assuming that is the case, when you made the copy from your working copy, you will have gotten a fairly complex transaction in the repository. As an example, this is the output I get from svn log using a fairly simple mixed revision example:
------------------------------------------------------------------------
r9 | markphip | 2007-01-08 09:34:06 -0500 (Mon, 08 Jan 2007) | 1 line
Changed paths:
A /branches/fromWC (from /trunk:3)
A /branches/fromWC/.classpath (from /trunk/.classpath:4)
A /branches/fromWC/.project (from /trunk/.project:4)
A /branches/fromWC/src (from /trunk/src:4)
A /branches/fromWC/src/org (from /trunk/src/org:5)
M /branches/fromWC/src/org/tigris/subversion/javahl/ChangePath.java
R /branches/fromWC/src/org/tigris/subversion/javahl/ClientException.java
(fro
m /trunk/src/org/tigris/subversion/javahl/ClientException.java:7)
R /branches/fromWC/src/org/tigris/subversion/javahl/JNIError.java (from /trunk/src/org/tigris/subversion/javahl/JNIError.java:8)

As you can see, I have files and folders copied from revisions 3, 4, 5, 7 and 8 and my shelf was created in revision 9. In this example, I know that revision 8 is the right value to use for the source of my copy, but in a more complex example, there may not be a right answer. Let's go back to our example, and just say that the shelf was created in revision 50 and it was created by copying trunk which was at revision 49. Assuming that is the case, then to merge the changes made in the shelf into your working copy, you would need to run a command like this:
svn merge url://server/repos/project1/trunk@49 url://server/repos/project1/branches/shelf@50

This tells Subversion to construct a diff of the changes between trunk @ revision 49 and the shelf @ revision 50 and then apply that diff to the current working copy. When this command runs, your working copy will now contain all of the changes you shelved. You can then go back to working on your change and eventually commit it to trunk or whatever the appropriate branch is to receive the change. Here is a screen shot of the same merge command from Subclipse:



Note that you just have to un-check the box that says to "Use From: URL". This then allows you to enter different "From" and "To" URL's and revisions.

To summarize this method, creating the shelf is easy, but using it again can be difficult. I will now show you what I think is a better, and much cleaner, method for creating shelves.

Better Method: Multiple Steps

There are really two main goals with this method:
  1. Remove the issue of mixed-revision working copies from the equation.
  2. Isolate the changes you want to shelve from the process of creating the shelf so that they are easy to merge later when you want to use them again.
This is always the technique that I use and it is generally much cleaner than the first approach.

The first thing to do is to create the shelf/branch. If you know your working copy is a little old, compared to the HEAD revision of your trunk or branch, then you can use the last revision you committed or updated to when creating the shelf. Otherwise, just use the HEAD revision:
svn copy -r HEAD url://server/repos/project1/trunk url://server/repos/project1/branches/shelf
This command is creating the shelf directly in the repository, based on whatever URL and revision your working copy is associated with. In this example, I used trunk. The next step is to use the svn switch command to switch your working copy so that it is pointing at the shelf URL:
svn switch url://server/repos/project1/branches/shelf

The switch command is also like an update. So if your working copy was not at the same revision you copied when you created the shelf, then it is possible that you will receive some updates, and perhaps even have some conflicts created in your working copy.

Here is a screen shot from Subclipse of the Create Branch dialog. Note the check box on the bottom that lets you create the branch and switch your working copy to it one step:



Once you have dealt with any conflicts that might been created in your working copy from the switch, you just need to commit your changes to the shelf using the normal commit command. When you are done, just use the switch command to switch your working copy back to trunk or wherever you need to be for your next task. Unlike the previous method, you will not need to cleanup your working copy when you do this.

At this point the shelf has been created and your changes have been committed in a way that will make it easy to merge them back later. The only part of this process that is potentially a bit more difficult than the first method is that you might have to resolve some conflicts before you can commit your changes to the shelf, and depending on why you are creating the shelf, you might not have the time to do this. If you create the shelf branch from the revision you have loaded in your working copy, you should minimize any potential for conflicts.

When you need to work on your shelved code again, it is easy. Just use the merge command to merge the changes from your shelf to your current working copy. Suppose you created the shelf with revision 44, spent some time resolving conflicts that were created when you switched, and then commited the changes to the shelf with revision 50. To merge the changes in the shelf to your current working copy, just run this command:
svn merge -r49:50 url://server/repos/project1/branches/shelf

This tells Subversion to merge the changes made in revision 50 to your current working copy (I could have used any value between 44 and 49 for the from revision). Since we separated the process of creating the shelf, from the changes you wanted to store in the shelf, this is an easy command to run. When the command completes your changes will have been merged into your working copy for you to pick up the work where you left off. When you are done, you just commit everything to trunk, or whatever branch is associated with your working copy, and you are done. The branch you created for the shelf can now be deleted from the repository if desired.

Summary
I went into a lot of detail in this article but hopefully you now know a lot more about how branching and merging work in Subversion. You can apply this information in your daily work process and use Subversion as a tool that helps you do your work better and more efficiently. I wrote this article under the pretense of the Shelving concept, but it is really just basic branching and merging in the end. I think one of the strengths of the Subversion design is that they kept it all very simple and you just use the basic functionality to build the process you want.

Friday, January 5, 2007

Combating Sudden Slowdowns in Subversion

Martin Tomes writes about a 20x performance slow-down in checkouts after installing a new anti-virus program. There have been a lot of reports over the years of various anti-virus programs causing slow-down's or other problems with Subversion on Windows. It is a good thing to check if suddenly you are having performance problems or other errors. Hopefully you are at least using a program that lets you fine-tune what and how it is doing the checking.

Another item that people have reported to cause slow-down's is the XP drive indexing. This can be disabled by right-clicking on the drive and choosing Properties. There is an option at the bottom of the General tab that controls whether Indexing is allowed. I do not know if other tools that index your drive, such as the Google Desktop can cause similar problems, but I suspect they can.