Adding git support to the ant SvnInfo task.

git svn users should now be able to correctly label a build with accurate svn information.

Review by: fabbott

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5008 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/CommandRunner.java b/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/CommandRunner.java
index ff47993..8f9881e 100644
--- a/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/CommandRunner.java
+++ b/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/CommandRunner.java
@@ -30,9 +30,11 @@
 
   /**
    * Returns the output of running a command as a string. Will fail if the
-   * invoked process returns a non-zero status code.
+   * invoked process returns a non-zero status code and
+   * <code>checkStatusCode</code> is <code>true</code>.
    */
-  public static String getCommandOutput(File workDir, String... cmd) {
+  public static String getCommandOutput(boolean checkStatusCode, File workDir,
+      String... cmd) {
     Process process = runCommandIgnoringErr(workDir, cmd);
     StringBuilder output = new StringBuilder();
     LineNumberReader lnr = new LineNumberReader(new InputStreamReader(
@@ -43,7 +45,7 @@
         output.append('\n');
       }
       int statusCode = process.waitFor();
-      if (statusCode != 0) {
+      if (checkStatusCode && statusCode != 0) {
         throw new BuildException("Non-zero status code result (" + statusCode
             + ") running command: " + makeCmdString(cmd));
       }
@@ -58,6 +60,14 @@
   }
 
   /**
+   * Returns the output of running a command as a string. Will fail if the
+   * invoked process returns a non-zero status code.
+   */
+  public static String getCommandOutput(File workDir, String... cmd) {
+    return getCommandOutput(true, workDir, cmd);
+  }
+
+  /**
    * Runs the specified command and returns the {@link Process}. The caller
    * must handle both the output and error streams to avoid blocking the
    * underlying process.
diff --git a/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/SvnInfo.java b/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/SvnInfo.java
index 706fc54..10d7b87 100644
--- a/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/SvnInfo.java
+++ b/build-tools/ant-gwt/src/com/google/gwt/ant/taskdefs/SvnInfo.java
@@ -79,6 +79,69 @@
       + URL_REGEX + ")\\s*");
 
   /**
+   * Returns true if this git working copy matches the specified svn revision,
+   * and also has no local modifications.
+   */
+  static boolean doesGitWorkingCopyMatchSvnRevision(File dir, String svnRevision) {
+    String workingRev = getGitWorkingRev(dir);
+    String targetRev = getGitRevForSvnRev(dir, svnRevision);
+    if (!workingRev.equals(targetRev)) {
+      return false;
+    }
+    String status = getGitStatus(dir);
+    return status.contains("nothing to commit (working directory clean)");
+  }
+
+  /**
+   * Returns the git commit number matching the specified svn revision.
+   */
+  static String getGitRevForSvnRev(File dir, String svnRevision) {
+    String output = CommandRunner.getCommandOutput(dir, "git", "svn",
+        "find-rev", "r" + svnRevision);
+    output = output.trim();
+    if (output.length() == 0) {
+      throw new BuildException("git svn find-rev didn't give any answer");
+    }
+    return output;
+  }
+
+  /**
+   * Runs "git status" and returns the result.
+   */
+  static String getGitStatus(File dir) {
+    // git status returns 1 for a status code, so just don't check it.
+    String output = CommandRunner.getCommandOutput(false, dir, "git", "status");
+    if (output.length() == 0) {
+      throw new BuildException("git status didn't give any answer");
+    }
+    return output;
+  }
+
+  /**
+   * Runs "git svn info", returning the output as a string.
+   */
+  static String getGitSvnInfo(File dir) {
+    String output = CommandRunner.getCommandOutput(dir, "git", "svn", "info");
+    if (output.length() == 0) {
+      throw new BuildException("git svn info didn't give any answer");
+    }
+    return output;
+  }
+
+  /**
+   * Returns the current git commit number of the working copy.
+   */
+  static String getGitWorkingRev(File dir) {
+    String output = CommandRunner.getCommandOutput(dir, "git", "rev-list",
+        "--max-count=1", "HEAD");
+    output = output.trim();
+    if (output.length() == 0) {
+      throw new BuildException("git rev-list didn't give any answer");
+    }
+    return output;
+  }
+
+  /**
    * Runs "svn info", returning the output as a string.
    */
   static String getSvnInfo(File dir) {
@@ -101,6 +164,17 @@
     return output;
   }
 
+  static boolean looksLikeGit(File dir) {
+    dir = dir.getAbsoluteFile();
+    while (dir != null) {
+      if (new File(dir, ".git").isDirectory()) {
+        return true;
+      }
+      dir = dir.getParentFile();
+    }
+    return false;
+  }
+
   /**
    * Returns <code>true</code> if the specified directory looks like an svn
    * working copy.
@@ -189,13 +263,20 @@
     }
 
     Info info;
-    if (!looksLikeSvn(workDirFile)) {
-      info = new Info("unknown", "unknown");
-    } else {
+    if (looksLikeSvn(workDirFile)) {
       info = parseInfo(getSvnInfo(workDirFile));
 
       // Use svnversion to get a more exact revision string.
       info.revision = getSvnVersion(workDirFile);
+    } else if (looksLikeGit(workDirFile)) {
+      info = parseInfo(getGitSvnInfo(workDirFile));
+
+      // Add a 'M' tag if this working copy is not pristine.
+      if (!doesGitWorkingCopyMatchSvnRevision(workDirFile, info.revision)) {
+        info.revision += "M";
+      }
+    } else {
+      info = new Info("unknown", "unknown");
     }
 
     getProject().setNewProperty(outprop, info.branch + "@" + info.revision);
diff --git a/build-tools/ant-gwt/test/com/google/gwt/ant/taskdefs/SvnInfoTest.java b/build-tools/ant-gwt/test/com/google/gwt/ant/taskdefs/SvnInfoTest.java
index 02ccdb2..2d09fd7 100644
--- a/build-tools/ant-gwt/test/com/google/gwt/ant/taskdefs/SvnInfoTest.java
+++ b/build-tools/ant-gwt/test/com/google/gwt/ant/taskdefs/SvnInfoTest.java
@@ -32,6 +32,79 @@
   private static final File dir = new File(".");
 
   /**
+   * A cached copy of 'git svn info' so we don't have to keep running it (makes
+   * the tests run faster).
+   */
+  private static String gitSvnInfo = null;
+
+  /**
+   * Check that this is a valid git rev.
+   */
+  private static void assertIsValidGitRev(String rev) {
+    assertNotNull(rev);
+    assertEquals(rev, 40, rev.length());
+    for (char ch : rev.toCharArray()) {
+      assertTrue(isHexDigit(ch));
+    }
+  }
+
+  private static String getGitSvnInfo() {
+    if (gitSvnInfo == null) {
+      gitSvnInfo = SvnInfo.getGitSvnInfo(dir);
+    }
+    return gitSvnInfo;
+  }
+
+  private static boolean isHexDigit(char ch) {
+    return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
+        || (ch >= 'a' && ch <= 'f');
+  }
+
+  /**
+   * Test that doesGitWorkingCopyMatchSvnRevision finishes.
+   */
+  public void testDoesGitWorkingCopyMatchSvnRevision() {
+    if (SvnInfo.looksLikeGit(dir)) {
+      Info info = SvnInfo.parseInfo(getGitSvnInfo());
+      SvnInfo.doesGitWorkingCopyMatchSvnRevision(dir, info.revision);
+    }
+  }
+
+  /**
+   * Test that getGitRevForSvnRev returns a 40-character git hash.
+   */
+  public void testGetGitRevForSvnRev() {
+    if (SvnInfo.looksLikeGit(dir)) {
+      Info info = SvnInfo.parseInfo(getGitSvnInfo());
+      String rev = SvnInfo.getGitRevForSvnRev(dir, info.revision);
+      assertIsValidGitRev(rev);
+    }
+  }
+
+  public void testGetGitStatus() {
+    if (SvnInfo.looksLikeGit(dir)) {
+      String status = SvnInfo.getGitStatus(dir);
+      assertNotNull(status);
+      assertTrue(!"".equals(status));
+    }
+  }
+
+  public void testGetGitSvnInfo() {
+    if (SvnInfo.looksLikeGit(dir)) {
+      String info = getGitSvnInfo();
+      assertNotNull(info);
+      assertTrue(!"".equals(info));
+    }
+  }
+
+  public void testGetGitSvnWorkingRev() {
+    if (SvnInfo.looksLikeGit(dir)) {
+      String rev = SvnInfo.getGitWorkingRev(dir);
+      assertIsValidGitRev(rev);
+    }
+  }
+
+  /**
    * If this is an svn working copy, just verify that "svn info" succeeds and
    * returns something.
    */