[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] New svnant task to fetch WC summary status

From: Matt Doran <matt.doran_at_papercut.biz>
Date: 2005-08-13 17:47:08 CEST

Mark Phippard wrote:

>I have never looked at the svnAnt code, but I imagine there is an
>ISVNClientAdapter available for you to use in the code. It then just has
>some really simple methods for doing stuff. You would want to look at
>getStatus()
>
>Mark
>
>
>

I've re-implemented my ant task that fetches a summary of working copy
status, storing the status in various ant properties. I thought I'd
post the first cut here for review before spending much more time on it.....

I've called the task "SummaryStatus" .... but I'm not sure it's the best
name.... suggestions?

The ant task can be called with the following syntax:

    <svn><summaryStatus path="wcPath" prefix="prefix."/></svn>

This will set the following properties:

    prefix.repos.url -> the repos URL of the
    WC root
    prefix.repos.path -> the repos path
    prefix.revision.max -> the highest revision
    number in the WC
    prefix.revision.max-with-flags -> the highest revision number in
    the WC plus flags (M - modified, X - mixed)
    prefix.revision.range -> the revision range
    similar to svnversion format. e.g. 1000:1010MX
    prefix.committed.max -> the max 'last committed
    revision' (Should we even bother with these? They don't add much
    value.)
    prefix.committed.max-with-flags -> the max 'last committed revision'
    with flags

I've taken the approach to specify a property prefix, so you can run
this multiple times for different working copies in the build (if
required). I noticed that some svnant tasks allow you to specify the
property name to store the result, but thought that given the number of
properties this task sets, this approach would be more practical.....
but I'm up for suggestions.

If we stick with this approach, are the property names appropriate?

Is there any other info that would be useful to add?

Other changes / comments:

    * The svnadapter patch fixes the build.properties to reference the
      correct libs..... without this the svnClientAdapter ant build fails.
    * SvnAnt was not using the latest trunk svnClientAdapter ... so I
      built a new version and used that.
    * Change SvnTask to load the JavaSVN adapter. It will be used in
      preference to the command-line client. Probably should make it
      configurable to choose your preference?? ... but this was just a
      quick fix
    * The tasks seems to work with well with both javahl and JavaSVN ...
      but the command-line adapter fails on fairly big working copy. I
      get an error "java.io.IOException: CreateProcess: svn info
      <multi-paths>". I haven't had a chance to look at it, but the
      command-line is very long (too long for windows?).

Look forward to your feedback ...

Regards,
Matt

Index: svnant/src/main/org/tigris/subversion/svnant/StatusSummary.java
===================================================================
--- svnant/src/main/org/tigris/subversion/svnant/StatusSummary.java (revision 0)
+++ svnant/src/main/org/tigris/subversion/svnant/StatusSummary.java (revision 0)
@@ -0,0 +1,214 @@
+package org.tigris.subversion.svnant;
+
+import java.io.File;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;
+import org.tigris.subversion.svnclientadapter.ISVNStatus;
+import org.tigris.subversion.svnclientadapter.SVNClientException;
+import org.tigris.subversion.svnclientadapter.SVNStatusKind;
+import org.tigris.subversion.svnclientadapter.SVNStatusUtils;
+
+/**
+ * Fetches a summary of working copy status.
+ *
+ * @author Matt Doran
+ * <a href="mailto:matt.doran@papercut.biz">matt.doran@papercut.biz</a>
+ */
+public class StatusSummary extends SvnCommand {
+
+ private File path;
+ private String prefix;
+
+ /**
+ * @see org.tigris.subversion.svnant.SvnCommand#execute(org.tigris.subversion.svnclientadapter.ISVNClientAdapter)
+ */
+ public void execute(ISVNClientAdapter svnClient) throws BuildException {
+ if (!getPath().exists() || !getPath().isDirectory()) {
+ throw new BuildException("Path is does not exist: " + getPath().getAbsolutePath());
+ }
+
+ SummaryInfo wcStatusInfo;
+
+ try {
+ wcStatusInfo = getWorkingCopyStatus(svnClient, getPath());
+ } catch (SVNClientException e) {
+ throw new BuildException("Can't get summary status for path " + getPath(), e);
+ }
+
+ // Save the status to ant properties.
+ Project p = getProject();
+
+ String prefix = getPrefix();
+ if (prefix == null) {
+ prefix = "";
+ }
+
+ p.setNewProperty(prefix + "repos.url", wcStatusInfo.reposURL);
+ p.setNewProperty(prefix + "repos.path", wcStatusInfo.reposPath);
+
+ p.setNewProperty(prefix + "revision.max", String.valueOf(wcStatusInfo.maxRevision));
+ p.setNewProperty(prefix + "revision.max-with-flags", wcStatusInfo.getMaxRevisionWithFlags());
+ p.setNewProperty(prefix + "revision.range", wcStatusInfo.getRevisionRange());
+
+ p.setNewProperty(prefix + "committed.max", String.valueOf(wcStatusInfo.maxCommitted));
+ p.setNewProperty(prefix + "committed.max-with-flags", wcStatusInfo.getMaxCommittedWithFlags());
+
+ if (wcStatusInfo.hasModified) {
+ p.setNewProperty(prefix + "modified", "true");
+ }
+
+ if (wcStatusInfo.hasMixed) {
+ p.setNewProperty(prefix + "mixed", "true");
+ }
+ }
+
+ /**
+ * Ensure we have a consistent and legal set of attributes
+ */
+ protected void validateAttributes() throws BuildException {
+ if (path == null) {
+ throw new BuildException("path attribute must be set");
+ }
+ }
+
+ /**
+ * Fetch the summary status information for the given working copy path.
+ * @param svnClient The svn client used to fetch the status.
+ * @param wcPathFile The working copy path.
+ * @return The <code>SummaryStatus</code> storing status information about the working copy.
+ * @throws SVNClientException Raised if there is a problem fetching working copy status.
+ */
+ private SummaryInfo getWorkingCopyStatus(ISVNClientAdapter svnClient, File wcPathFile) throws SVNClientException {
+ SummaryInfo wcStatus = new SummaryInfo();
+ wcStatus.wcPath = wcPathFile.getAbsolutePath();
+
+ ISVNStatus rootStatus = svnClient.getSingleStatus(wcPathFile);
+ wcStatus.reposURL = rootStatus.getUrl().toString();
+
+ String[] pathSegs = rootStatus.getUrl().getPathSegments();
+ StringBuffer pathBuffer = new StringBuffer();
+ for (int i = 0; i < pathSegs.length; i++) {
+ pathBuffer.append('/').append(pathSegs[i]);
+ }
+ wcStatus.reposPath = pathBuffer.toString();
+
+ ISVNStatus[] statuses = svnClient.getStatus(wcPathFile, true, true);
+ for (int i = 0; i < statuses.length; i++) {
+ ISVNStatus status = statuses[i];
+
+ if (!SVNStatusUtils.isManaged(status)) {
+ // Don't care about unversioned files
+ continue;
+ }
+
+ if (!wcStatus.hasModified
+ && (status.getTextStatus() != SVNStatusKind.NORMAL
+ || status.getPropStatus() != SVNStatusKind.NORMAL)) {
+ wcStatus.hasModified = true;
+ }
+
+ long rev = status.getRevision().getNumber();
+ if (rev > wcStatus.maxRevision) {
+ wcStatus.maxRevision = rev;
+ }
+
+ if (rev < wcStatus.minRevision) {
+ wcStatus.minRevision = rev;
+ }
+
+ long committedRev = status.getLastChangedRevision().getNumber();
+ if (committedRev > wcStatus.maxCommitted) {
+ wcStatus.maxCommitted = committedRev;
+ }
+
+ }
+
+ if (wcStatus.minRevision != wcStatus.maxRevision) {
+ wcStatus.hasMixed = true;
+ }
+
+ return wcStatus;
+ }
+
+ /**
+ * Holds summary status information about a working copy path.
+ *
+ * @author Matt Doran (matt.doran@papercut.biz)
+ */
+ private static class SummaryInfo {
+ public String wcPath;
+ public long maxRevision = Long.MIN_VALUE;
+ public long maxCommitted = Long.MIN_VALUE;
+ public long minRevision = Long.MAX_VALUE;
+ public boolean hasModified = false;
+ public boolean hasMixed = false;
+ public String reposURL;
+ public String reposPath;
+
+ /**
+ * Get the max revision with flags indicating other status information.
+ * @return The max revision with flags.
+ */
+ public String getMaxRevisionWithFlags() {
+ return String.valueOf(maxRevision) + getFlags();
+ }
+
+ /**
+ * Gets the revision range including flags. If there is a mixed working copy the format of the
+ * range is <min>:<max><flags>.
+ *
+ * If the working copy is not mixed, the same output as <code>getMaxRevisionWithFlags()</code> is returned.
+ *
+ * @return The revision range string.
+ */
+ public String getRevisionRange() {
+ if (hasMixed) {
+ return String.valueOf(minRevision) + ":" + getMaxRevisionWithFlags();
+ } else {
+ return getMaxRevisionWithFlags();
+ }
+ }
+
+ public String getMaxCommittedWithFlags() {
+ return String.valueOf(maxCommitted) + getFlags();
+ }
+
+ /**
+ * Returns status flags string. (M - modified, X - mixed).
+ * @return The status flags.
+ */
+ private String getFlags() {
+ return (hasModified ? "M" : "") + (hasMixed ? "X" : "");
+ }
+ }
+
+ /**
+ * @return Returns the path.
+ */
+ public File getPath() {
+ return path;
+ }
+
+ /**
+ * @param path The path to set.
+ */
+ public void setPath(File path) {
+ this.path = path;
+ }
+
+ /**
+ * @return Returns the prefix.
+ */
+ public String getPrefix() {
+ return prefix;
+ }
+
+ /**
+ * @param prefix The prefix to set.
+ */
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+}

Property changes on: svnant\src\main\org\tigris\subversion\svnant\StatusSummary.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain
Name: svn:keywords
   + Id Rev Date Author
Name: svn:eol-style
   + native

Index: svnant/src/main/org/tigris/subversion/svnant/SvnTask.java
===================================================================
--- svnant/src/main/org/tigris/subversion/svnant/SvnTask.java (revision 1534)
+++ svnant/src/main/org/tigris/subversion/svnant/SvnTask.java (working copy)
@@ -65,6 +65,7 @@
 import org.tigris.subversion.svnclientadapter.SVNClientAdapterFactory;
 import org.tigris.subversion.svnclientadapter.SVNClientException;
 import org.tigris.subversion.svnclientadapter.commandline.CmdLineClientAdapterFactory;
+import org.tigris.subversion.svnclientadapter.javahl.JavaSvnClientAdapterFactory;
 import org.tigris.subversion.svnclientadapter.javahl.JhlClientAdapterFactory;
 
 /**
@@ -84,6 +85,8 @@
     
     private static boolean javahlAvailableInitialized = false;
     private static boolean javahlAvailable;
+ private static boolean javaSVNAvailableInitialized = false;
+ private static boolean javaSVNAvailable;
     private static boolean commandLineAvailableInitialized = false;
     private static boolean commandLineAvailable;
 
@@ -195,6 +198,10 @@
     public void addCreateRepository(CreateRepository a) {
         commands.add(a);
     }
+
+ public void addSummaryStatus(StatusSummary a) {
+ commands.add(a);
+ }
 
     public void addStatus(Status a) {
             commands.add(a);
@@ -234,6 +241,28 @@
     }
     
     /**
+ * check if JavaSVN is available
+ * @return true if JavaSVN is available
+ */
+ private boolean isJavaSVNAvailable() {
+ if (javaSVNAvailableInitialized == false) {
+ // we don't initiliaze javahlAvailable in the static field because we
+ // don't want the check to occur if javahl is set to false
+ try {
+ JavaSvnClientAdapterFactory.setup();
+ } catch (SVNClientException e) {
+ // if an exception is thrown, JavaSVN is not available or
+ // already registered ...
+ e.printStackTrace();
+ }
+ javaSVNAvailable =
+ SVNClientAdapterFactory.isSVNClientAvailable(JavaSvnClientAdapterFactory.JAVASVN_CLIENT);
+ javaSVNAvailableInitialized = true;
+ }
+ return javaSVNAvailable;
+ }
+
+ /**
      * check if command line interface is available
      * @return true if command line interface is available
      */
@@ -261,12 +290,17 @@
             log("Using javahl");
         }
         else
+ if (isJavaSVNAvailable()) {
+ svnClient = SVNClientAdapterFactory.createSVNClient(JavaSvnClientAdapterFactory.JAVASVN_CLIENT);
+ log("Using javasvn");
+ }
+ else
         if (isCommandLineAvailable()) {
             svnClient = SVNClientAdapterFactory.createSVNClient(CmdLineClientAdapterFactory.COMMANDLINE_CLIENT);
             log("Using command line interface");
         }
         else
- throw new BuildException("Cannot use javahl nor command line svn client");
+ throw new BuildException("Cannot use javahl, JavaSVN nor command line svn client");
         
 
         if (username != null)

Index: build.properties
===================================================================
--- build.properties (revision 1534)
+++ build.properties (working copy)
@@ -30,5 +30,5 @@
 svnjavahl.jar=${lib.dir}/svnjavahl.jar
 jakarta-regexp.jar=${lib.dir}/jakarta-regexp-1.3.jar
 commons-lang.jar=${lib.dir}/commons-lang-2.0.jar
-javasvn.jar=${lib.dir}/javasvn-0.8.0.jar
-jsch.jar=${lib.dir}/javasvn-0.1.20.jar
\ No newline at end of file
+javasvn.jar=${lib.dir}/javasvn.jar
+jsch.jar=${lib.dir}/jsch.jar
\ No newline at end of file
Received on Sun Aug 14 01:47:08 2005

This is an archived mail posted to the Subclipse Dev mailing list.