0

Export Subversion Files Changed between Two Revision Numbers

Subversion, Java

Update 6/2/2008: in response to Mike's comment, here is a code snippet for how to use the SVNChangedFiles class, which will export the files between the two revision numbers from a Subversion branch into the directory you specify:

SVNChangedFiles ecf = new SVNChangedFiles(
  [SVNbranchURL], 
  [SVNusername], 
  [SVNpassword], 
  [SVNstartRevisionNumber], 
  [SVNendRevisionNumber], 
  [PathToLocalDirectory], 
  [useAncestry]
);

ecf.export();

The way the product I work on is delivered sometimes requires being able to issue just a subset of files that have changed since a full release as a package. Subversion as-is doesn't really want to help you with this problem. You can do something like:

svn diff -r[###]:[###] http://[URL to Repository]/[branch] --summarize

But then you are left to do something with those results in order to actually pull those particular files.

The Tortois SVN client can help you with this problem, but nothing something I want to script from Ant build files. Fortunately SVN has an API that allows you hack things the way you like. And there are some good Java Subversion client libraries for this purpose, such as SVNKit.

So here is something I hacked together for pulling changed files that can also work as an Ant task.

SVNChangedFiles

import java.io.File;
import java.util.ArrayList;

import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.wc.ISVNDiffStatusHandler;
import org.tmatesoft.svn.core.wc.SVNDiffClient;
import org.tmatesoft.svn.core.wc.SVNDiffStatus;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc.SVNUpdateClient;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

public class SVNChangedFiles {
	private SVNURL branchURL;
	private String username;
	private String password;
	private SVNRevision startingRevision;
	private SVNRevision endingRevision;
	private String destinationDirectory;

	private ISVNAuthenticationManager authManager;

	/**
	 * <p>
	 * Tested with SVNKit 1.1.4
	 * </p>
	 * <p>
	 * Use to export added or modified files from a Subversion repository at
	 * branchURL between startingRevision and endingRevision revisions to the specified
	 * destinationDirectory.
	 * </p>
	 *
	 * @param branchURL	fully qualified URL of the SVN repository (i.e. "http://[domain]/[path-to-svn]/[branch]")
	 * @param username	SVN username
	 * @param password	SVN password
	 * @param startingRevision	Starting revision number for the branch specified
	 * @param endingRevision	Ending revision number for the branch specified
	 * @param destinationDirectory	Root directory where to export files. <i>Files with same name will be overwritten without warning.</i>
	 */
	public SVNChangedFiles(String branchURL, String username, String password,
			long startingRevision, long endingRevision,
			String destinationDirectory) {
		super();
		DAVRepositoryFactory.setup();
		try {
			this.branchURL = SVNURL.parseURIEncoded(branchURL);
			this.username = username;
			this.password = password;
			this.startingRevision = SVNRevision.create(startingRevision);
			this.endingRevision = SVNRevision.create(endingRevision);
			this.destinationDirectory = destinationDirectory;

			this.authManager = SVNWCUtil.createDefaultAuthenticationManager(this.username, this.password);
		} catch (SVNException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * <p>Calls {@link SVNChangedFiles#export(ArrayList)} passing in a null.
	 */
	public void export() { export(null); }

	/**
	 * <p>
	 * Exports files added or modified between the startingRevision and endingRevision revision numbers,
	 * including creating sub-directories, relative to the destinationDirectory.</p>
	 * </p>
	 * <p>
	 * <i>Files of the same name will be overwritten without warning.</i>
	 * </p>
	 *
	 * @param changes ArrayList of SVNDiffStatus objects for export, passing null will populate with the results of {@link SVNChangedFiles#list()}
	 */
	public void export(ArrayList changes) {
		if (changes == null) changes = list();
		SVNUpdateClient updateClient = new SVNUpdateClient(authManager, SVNWCUtil.createDefaultOptions(true));
		try {

			for (int idx = 0; idx < changes.size(); idx++) {
				SVNDiffStatus change = (SVNDiffStatus) changes.get(idx);
				File destination = new File(destinationDirectory + "\\" + change.getPath());
				updateClient.doExport(change.getURL(), destination, this.endingRevision, this.endingRevision, null, true, false);
			}
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * <p>
	 * Returns an {@link java.util.ArrayList} of {@link org.tmatesoft.svn.core.wc.SVNDiffStatus} objects
	 * representing all files exported or modified on the given branchURL between the startingRevision and endingRevision
	 * revisions.
	 * </p>
	 * @return {@link java.util.ArrayList} of {@link org.tmatesoft.svn.core.wc.SVNDiffStatus}
	 */
	public ArrayList list() {
		try {
			SVNDiffClient diffClient = new SVNDiffClient(authManager, SVNWCUtil.createDefaultOptions(true));

			ArrayList changes = new ArrayList();

			// adds to changes
			ImplISVNDiffStatusHandler handler = new ImplISVNDiffStatusHandler(changes);

			diffClient.doDiffStatus(this.branchURL, this.startingRevision,
					this.branchURL, this.endingRevision, true, false, handler);

			return changes;
		} catch (SVNException e) {
			throw new RuntimeException(e);
		}
	}

	private static class ImplISVNDiffStatusHandler implements ISVNDiffStatusHandler {

		private ArrayList changes;

		public ImplISVNDiffStatusHandler(ArrayList changes) {
			this.changes = changes;
		}

		public void handleDiffStatus(SVNDiffStatus status) throws SVNException {
			if (status.getKind() == SVNNodeKind.FILE && (status.getModificationType() == SVNStatusType.STATUS_ADDED || status.getModificationType() == SVNStatusType.STATUS_MODIFIED))
				changes.add(status);
		}

	}

}

Instantiate and call the export() method to pull the files changed on the branch between the starting and ending revision numbers into the specified destination directory.

Building this requires the SVNKit library.

Ant Task

import java.util.ArrayList;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

import org.tmatesoft.svn.core.wc.SVNDiffStatus;

public class SVNExportChangedFilesAntTask extends Task {
	private String branch;
	private String username;
	private String password;
	private long start;
	private long end;
	private String destination;

	public void execute() throws BuildException {
		// TODO Auto-generated method stub
		super.execute();
		SVNChangedFiles changedFiles = new SVNChangedFiles(getBranch(), getUsername(), getPassword(), getStart(), getEnd(), getDestination());

		System.out.println("Export Root Directory:\t" + getDestination());

		ArrayList changes = changedFiles.list();
		for (int idx = 0; idx < changes.size(); idx++) {
			SVNDiffStatus change = (SVNDiffStatus) changes.get(idx);
			ArrayList singleChange = new ArrayList();
			singleChange.add(change);
			changedFiles.export(singleChange);
			System.out.println("\tExported:\t" + change.getFile());
		}
	}

	public String getBranch() {
		return branch;
	}
	public void setBranch(String branch) {
		this.branch = branch;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public long getStart() {
		return start;
	}
	public void setStart(long start) {
		this.start = start;
	}
	public long getEnd() {
		return end;
	}
	public void setEnd(long end) {
		this.end = end;
	}
	public String getDestination() {
		return destination;
	}
	public void setDestination(String destination) {
		this.destination = destination;
	}


}

I leave configuring this with Ant as an excercise for the reader. Once configured it is called as such:

<exportChangedFiles
	branch="[Branch URL]"
	start="[Start Revision Number]"
	end="[End Revision Number]"
	destination="[Full path of directory to export files to]"
	password="[Password]"
	username="[Username]"
/>

Building this requires the Apache Ant library.

 

Mike said:
 
Could you post how you are accomplishing this? Thanks
 
posted 536 days ago
View Replies (2) || Add Comment Reply to: this comment OR this thread
 
.: HIDE REPLIES :.
 
I added a usage example at the top. Hopefully that helps.
 
posted 536 days ago
Add Comment Reply to: this comment OR this thread
 
Mike said:
 
Thanks, I am interested in the ant task. I'll have to give it a try.
 
posted 536 days ago
Add Comment Reply to: this comment OR this thread
 
Mike Henke said:
 
Here is my attempt @ an ant for revision by range - http://tinyurl.com/4lmjoq
 
posted 523 days ago
Add Comment Reply to: this comment OR this thread
 
Mike Henke said:
 
Matthew, a compiled jar would be great. Maybe put it out on your website or google code for people to download.
 
posted 522 days ago
View Replies (2) || Add Comment Reply to: this comment OR this thread
 
.: HIDE REPLIES :.
 
Hmmm, no easy way I can find on InstantSpot to upload some code and make it downloadable. For now, guess I'll look to send to you via email attachment.
 
posted 520 days ago
Add Comment Reply to: this comment OR this thread
 
Ernst said:
 
Great post OrangePips! Can you please send me the jar too?
 
posted 426 days ago
Add Comment Reply to: this comment OR this thread
 

Search