From 24ca28a3a64af28077f65d6811c762b7a39e63f6 Mon Sep 17 00:00:00 2001 From: Marko Bakovic Date: Wed, 25 Apr 2018 15:40:40 +0100 Subject: [PATCH] Use only jgit (#94) * drop native git * to string * remove walk.close --- .../gradle/gitversion/JGitDescribe.java | 22 +- .../gradle/gitversion/NativeGitDescribe.java | 107 --------- .../gradle/gitversion/VersionDetails.java | 35 ++- .../gradle/gitversion/JGitDescribeTest.java | 223 ++++++++++++++++++ 4 files changed, 253 insertions(+), 134 deletions(-) delete mode 100644 src/main/java/com/palantir/gradle/gitversion/NativeGitDescribe.java create mode 100644 src/test/java/com/palantir/gradle/gitversion/JGitDescribeTest.java diff --git a/src/main/java/com/palantir/gradle/gitversion/JGitDescribe.java b/src/main/java/com/palantir/gradle/gitversion/JGitDescribe.java index 94481d38..314d7018 100644 --- a/src/main/java/com/palantir/gradle/gitversion/JGitDescribe.java +++ b/src/main/java/com/palantir/gradle/gitversion/JGitDescribe.java @@ -64,6 +64,7 @@ private List revList(ObjectId initialObjectId) throws IOException { Repository repo = git.getRepository(); try (RevWalk walk = new RevWalk(repo)) { + walk.setRetainBody(false); RevCommit head = walk.parseCommit(initialObjectId); while (true) { @@ -88,16 +89,19 @@ private Map mapCommitsToTags(Git git) { // Maps commit hash to list of all refs pointing to given commit hash. // All keys in this map should be same as commit hashes in 'git show-ref --tags -d' Map commitHashToTag = new HashMap<>(); - for (Map.Entry entry : git.getRepository().getTags().entrySet()) { - RefWithTagName refWithTagName = new RefWithTagName(entry.getValue(), entry.getKey()); - - ObjectId peeledRef = refWithTagName.getRef().getPeeledObjectId(); - if (peeledRef == null) { - // lightweight tag (commit object) - updateCommitHashMap(commitHashToTag, comparator, entry.getValue().getObjectId(), refWithTagName); + Repository repository = git.getRepository(); + for (Map.Entry entry : repository.getTags().entrySet()) { + Ref peeledRef = repository.peel(entry.getValue()); + RefWithTagName refWithTagName = new RefWithTagName(peeledRef, entry.getKey()); + + // Peel ref object + ObjectId peeledObjectId = peeledRef.getPeeledObjectId(); + if (peeledObjectId == null) { + // Lightweight tag (commit object) + updateCommitHashMap(commitHashToTag, comparator, peeledRef.getObjectId(), refWithTagName); } else { - // annotated tag (tag object) - updateCommitHashMap(commitHashToTag, comparator, peeledRef, refWithTagName); + // Annotated tag (tag object) + updateCommitHashMap(commitHashToTag, comparator, peeledObjectId, refWithTagName); } } return commitHashToTag; diff --git a/src/main/java/com/palantir/gradle/gitversion/NativeGitDescribe.java b/src/main/java/com/palantir/gradle/gitversion/NativeGitDescribe.java deleted file mode 100644 index 835444c2..00000000 --- a/src/main/java/com/palantir/gradle/gitversion/NativeGitDescribe.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.palantir.gradle.gitversion; - -import com.google.common.base.Preconditions; -import com.google.common.base.Splitter; -import com.google.common.collect.Sets; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -/** - * Mimics git describe by using rev-list to support versions of git < 1.8.4 - */ -class NativeGitDescribe implements GitDescribe { - private static final Logger log = LoggerFactory.getLogger(NativeGitDescribe.class); - - private static final Splitter LINE_SPLITTER = Splitter.on(System.getProperty("line.separator")).omitEmptyStrings(); - private static final Splitter WORD_SPLITTER = Splitter.on(" ").omitEmptyStrings(); - - private final File directory; - - NativeGitDescribe(File directory) { - this.directory = directory; - } - - private String runGitCmd(String... commands) throws IOException, InterruptedException { - List cmdInput = new ArrayList<>(); - cmdInput.add("git"); - cmdInput.addAll(Arrays.asList(commands)); - ProcessBuilder pb = new ProcessBuilder(cmdInput); - pb.directory(directory); - pb.redirectErrorStream(true); - - Process process = pb.start(); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - - StringBuilder builder = new StringBuilder(); - String line = null; - while ((line = reader.readLine()) != null) { - builder.append(line); - builder.append(System.getProperty("line.separator")); - } - - int exitCode = process.waitFor(); - if (exitCode != 0) { - return ""; - } - - return builder.toString().trim(); - } - - @Override - public String describe(String prefix) { - if (!gitCommandExists()) { - return null; - } - - try { - // Get SHAs of all tags, we only need to search for these later on - Set tagRefs = Sets.newHashSet(); - for (String tag : LINE_SPLITTER.splitToList(runGitCmd("show-ref", "--tags", "-d"))) { - List parts = WORD_SPLITTER.splitToList(tag); - Preconditions.checkArgument(parts.size() == 2, "Could not parse output of `git show-ref`: %s", parts); - tagRefs.add(parts.get(0)); - } - - List revs = LINE_SPLITTER.splitToList(runGitCmd("rev-list", "--first-parent", "HEAD")); - for (int depth = 0; depth < revs.size(); depth++) { - String rev = revs.get(depth); - if (tagRefs.contains(rev)) { - String exactTag = runGitCmd("describe", "--tags", "--exact-match", "--match=" + prefix + "*", rev); - if (!exactTag.isEmpty()) { - return depth == 0 ? - exactTag : String.format("%s-%s-g%s", exactTag, depth, GitUtils.abbrevHash(revs.get(0))); - } - } - } - - // No tags found, so return commit hash of HEAD - return GitUtils.abbrevHash(runGitCmd("rev-parse", "HEAD")); - } catch (Exception e) { - log.debug("Native git describe failed: {}", e); - return null; - } - } - - private boolean gitCommandExists() { - try { - // verify that "git" command exists (throws exception if it does not) - Process gitVersionProcess = new ProcessBuilder("git", "version").start(); - if (gitVersionProcess.waitFor() != 0) { - throw new IllegalStateException("error invoking git command"); - } - return true; - } catch (Exception e) { - log.debug("Native git command not found: {}", e); - return false; - } - } -} diff --git a/src/main/java/com/palantir/gradle/gitversion/VersionDetails.java b/src/main/java/com/palantir/gradle/gitversion/VersionDetails.java index 92655b70..a7bf9b99 100644 --- a/src/main/java/com/palantir/gradle/gitversion/VersionDetails.java +++ b/src/main/java/com/palantir/gradle/gitversion/VersionDetails.java @@ -49,7 +49,7 @@ private String description() { String rawDescription = expensiveComputeRawDescription(); maybeCachedDescription = rawDescription == null ? - rawDescription : rawDescription.replaceFirst("^" + args.getPrefix(), ""); + null : rawDescription.replaceFirst("^" + args.getPrefix(), ""); return maybeCachedDescription; } @@ -59,23 +59,7 @@ private String expensiveComputeRawDescription() { return null; } - String nativeGitDescribe = new NativeGitDescribe(git.getRepository().getDirectory()) - .describe(args.getPrefix()); - String jgitDescribe = new JGitDescribe(git) - .describe(args.getPrefix()); - - // If native failed, return JGit one - if (nativeGitDescribe == null) { - return jgitDescribe; - } - - // If native succeeded, make sure it's same as JGit one - if (!nativeGitDescribe.equals(jgitDescribe)) { - throw new IllegalStateException(String.format("Inconsistent git describe: native was %s and jgit was %s. " + - "Please report this on github.com/palantir/gradle-git-version", nativeGitDescribe, jgitDescribe)); - } - - return jgitDescribe; + return new JGitDescribe(git).describe(args.getPrefix()); } private boolean isRepoEmpty() { @@ -141,4 +125,19 @@ public String getBranchName() throws IOException { return ref.getName().substring(Constants.R_HEADS.length()); } + + @Override + public String toString() { + try { + return String.format("VersionDetails(%s, %s, %s, %s, %s)", + getVersion(), + getGitHash(), + getGitHashFull(), + getBranchName(), + getIsCleanTag() + ); + } catch (IOException e) { + return null; + } + } } diff --git a/src/test/java/com/palantir/gradle/gitversion/JGitDescribeTest.java b/src/test/java/com/palantir/gradle/gitversion/JGitDescribeTest.java new file mode 100644 index 00000000..3f233073 --- /dev/null +++ b/src/test/java/com/palantir/gradle/gitversion/JGitDescribeTest.java @@ -0,0 +1,223 @@ +package com.palantir.gradle.gitversion; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.MergeCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JGitDescribeTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private File projectDir; + private Git git; + private PersonIdent identity = new PersonIdent("name", "email@address"); + + @Before + public void before() throws GitAPIException { + projectDir = temporaryFolder.getRoot(); + git = Git.init().setDirectory(projectDir).call(); + } + + @Test + public void test_on_annotated_tag() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(true).setMessage("1.0.0").setName("1.0.0").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_on_lightweight_tag() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(false).setName("1.0.0").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_annotated_tag_with_merge_commit() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(true).setMessage("1.0.0").setName("1.0.0").call(); + String master = git.getRepository().getFullBranch(); + Ref hotfixBranch = git.branchCreate().setName("hotfix").call(); + git.checkout().setName(hotfixBranch.getName()).call(); + git.commit().setMessage("hot fix for issue").call(); + git.tag().setAnnotated(true).setMessage("1.0.0-hotfix").setName("1.0.0-hotfix").call(); + git.checkout().setName(master).call(); + git.merge().include(git.getRepository().getRef("hotfix")) + .setFastForward(MergeCommand.FastForwardMode.NO_FF).setMessage("merge commit").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_annotated_tag_after_merge_commit() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(true).setMessage("1.0.0").setName("1.0.0").call(); + String master = git.getRepository().getFullBranch(); + Ref hotfixBranch = git.branchCreate().setName("hotfix").call(); + git.checkout().setName(hotfixBranch.getName()).call(); + git.commit().setMessage("hot fix for issue").call(); + git.tag().setAnnotated(true).setMessage("1.0.0-hotfix").setName("1.0.0-hotfix").call(); + git.checkout().setName(master).call(); + git.merge().include(git.getRepository().getRef("hotfix")) + .setFastForward(MergeCommand.FastForwardMode.NO_FF).setMessage("merge commit").call(); + git.tag().setAnnotated(true).setMessage("2.0.0").setName("2.0.0").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_head_detached() throws Exception { + git.add().addFilepattern(".").call(); + RevCommit commit1 = git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(true).setMessage("1.0.0").setName("1.0.0").call(); + git.commit().setMessage("commit 2").call(); + git.checkout().setName(commit1.getId().getName()).call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_commit_after_annotated_tag() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(true).setMessage("1.0.0").setName("1.0.0").call(); + git.add().addFilepattern(".").call(); + git.commit().setMessage("added some stuff").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_multiple_commits_after_annotated_tag() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(true).setMessage("1.0.0").setName("1.0.0").call(); + for (int i = 0; i < 100; i++) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("commit-" + i).call(); + } + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_commit_after_lightweight_tag() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(false).setName("1.0.0").call(); + git.add().addFilepattern(".").call(); + git.commit().setMessage("added some stuff").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_multiple_commits_after_lightweight_tag() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(false).setName("1.0.0").call(); + for (int i = 0; i < 100; i++) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("commit-" + i).call(); + } + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_multiple_tags_annotated_is_chosen() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(false).setName("1.0.0").call(); + git.tag().setAnnotated(true).setName("2.0.0").call(); + git.tag().setAnnotated(false).setName("3.0.0").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_multiple_annotated_tags_most_recent_is_chosen() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(true).setTagger( + new PersonIdent(identity, new Date(0, 0, 0))).setName("1.0.0").call(); + git.tag().setAnnotated(true).setTagger( + new PersonIdent(identity, new Date(0, 0, 10))).setName("2.0.0").call(); + git.tag().setAnnotated(true).setTagger( + new PersonIdent(identity, new Date(0, 0, 5))).setName("3.0.0").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + @Test + public void test_multiple_lightweight_tags_smaller_is_chosen() throws Exception { + git.add().addFilepattern(".").call(); + git.commit().setMessage("initial commit").call(); + git.tag().setAnnotated(false).setName("2.0.0").call(); + git.tag().setAnnotated(false).setName("1.0.0").call(); + git.tag().setAnnotated(false).setName("3.0.0").call(); + + assertThat(jgitDescribe()).isEqualTo(nativeGitDescribe()); + } + + private String jgitDescribe() throws GitAPIException { + return new JGitDescribe(git).describe(""); + } + + private String nativeGitDescribe() throws IOException, InterruptedException, RuntimeException { + return runGitCmd(projectDir, "describe", "--tags", "--always", "--first-parent", "HEAD"); + } + + private String runGitCmd(File directory, String... commands) + throws IOException, InterruptedException, RuntimeException { + List cmdInput = new ArrayList<>(); + cmdInput.add("git"); + cmdInput.addAll(Arrays.asList(commands)); + ProcessBuilder pb = new ProcessBuilder(cmdInput); + pb.directory(directory); + pb.redirectErrorStream(true); + + Process process = pb.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + StringBuilder builder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + builder.append(line); + builder.append(System.getProperty("line.separator")); + } + + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new RuntimeException(String.format("running git commands '%s' failed with exit code %s", + cmdInput, exitCode)); + } + + return builder.toString().trim(); + } +}