Overview

SVN Rule: Don’t commit on a tag – unless it is not a tag!

No Comments

Imagine yourself committing your sources to your subversion repository after a hard day’s work and having this message pop up:

svn-tag

Yikes! You are pretty sure you did modify the correct files and did not mess up your trunk/tag SVN meta files (again).

Relax, most probably it’s just your Subversive plug-in playing tricks on you. We recently updated one of our customer’s development environment to Ganymede with Subversive as the Subversion plug-in and encountered no problems so far. The only oddity that surfaced quite fast was this message.

Taking a closer look we noticed that Subversive’s method to check whether you are on a tag or not is not as smart as you might expect. Every time you try to commit something that has the string “tags” in it, subversive will complain. We have packages that contain JSP tags (com.acme.tags.FooTag), and every time we try to commit a change this warning pops up.

Obviously this is nothing to be afraid of, just confirm your commit and you are good to go. If you want to know why this irritating behaviour occurs, read on.

This part of the CommitAction class is responsible for the nag-screen:

if (SVNUtility.isTagOperated(allResources)) {
	TagModifyWarningDialog dlg = new TagModifyWarningDialog(this.getShell());
	if (dlg.open() != 0) {
		return;
	}
}

SVNUtility.isTagOperated() checks for the kind of the “root” of the resource to be committed.

if (((IRepositoryRoot)SVNRemoteStorage.instance().asRepositoryResource(resources[i]).getRoot()).getKind() == IRepositoryRoot.KIND_TAGS) {
	return true;
}

I put “root” in quotations, because one could argue what a “root” really is. Just ask yourself what the root of “/usr/local/share/svn/trunk/com/acme/tags/FooTag.java” is. I’d say it’s “/” or “/usr/” from a file system or “/trunk/” from a repository point of view. But SVNRepositoryResource.getRoot() thinks different:

public IRepositoryResource getRoot() {
	if (this.root == null) {
		IRepositoryResource parent = this;
		while (!(parent instanceof IRepositoryRoot)) {
			parent = parent.getParent();
		}
		this.root = (IRepositoryRoot)parent;
	}
	return this.root;
}

SVNRepositoryLocation.getParent() returns a specific object if the last segment of the URL path contains one of SVN’s keywords:

Path urlPath = new Path(url);
String name = urlPath.lastSegment();
 
if (location.isStructureEnabled()) {
	if (name.equals(location.getTrunkLocation())) {
		return new SVNRepositoryTrunk(location, url, SVNRevision.HEAD);
	}
	if (name.equals(location.getTagsLocation())) {
		return new SVNRepositoryTags(location, url, SVNRevision.HEAD);
	}
	if (name.equals(location.getBranchesLocation())) {
		return new SVNRepositoryBranches(location, url, SVNRevision.HEAD);
	}
}

Subversive treats the first occurrence as the root. Because of the package name name.equals(location.getTagsLocation()) matches and a SVNRepositoryTags object is returned.

Funny thing is that the SVNRepositoryTags class (which is of IRepositoryRoot.KIND_TAGS, of course) extends SVNRepositoryRootBase which implements IRepositoryRoot. Here we go, the while loop’s condition of getRoot() is no longer met, so it returns the current IRepositoryResource which leads to the positive return value of SVNUtility.isTagOperated() – even tough we a not working on a SVN tag.

This is a minor problem, because it just leads to a warning, not an error. IMHO going down all the way of the urlPath could solve to problem. The last occurrence of an instance of IRepositoryRoot should be used rather than the fist. This would also work for repositories regardless of the repository layout (global vs. per project trunk/tags/branches). Subversion implementations should not interfere with package layouts, even if SVN keywords are used.

Comment

Your email address will not be published. Required fields are marked *