Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* native vs jgit * jgit * fix jgit * remove safe git * refactor * dual impl * comparator * address comments * readme * add debug logs * Smaller scope variables * return jgit * map to single ref
- Loading branch information
Showing
9 changed files
with
444 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
src/main/groovy/com/palantir/gradle/gitversion/GitDescribe.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.palantir.gradle.gitversion | ||
|
||
interface GitDescribe { | ||
|
||
/** | ||
* Mimics behaviour of 'git describe --tags --always --first-parent --match=${prefix}*' | ||
* Method returns null if repository is empty. | ||
*/ | ||
String describe(String prefix) | ||
} |
35 changes: 35 additions & 0 deletions
35
src/main/groovy/com/palantir/gradle/gitversion/GitUtils.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.palantir.gradle.gitversion | ||
|
||
import org.eclipse.jgit.api.DescribeCommand | ||
import org.eclipse.jgit.api.Git | ||
import org.eclipse.jgit.lib.ObjectId | ||
import org.eclipse.jgit.lib.Ref | ||
|
||
class GitUtils { | ||
|
||
static final int SHA_ABBR_LENGTH = 7 | ||
|
||
static String abbrevHash(String s) { | ||
return s.substring(0, SHA_ABBR_LENGTH) | ||
} | ||
|
||
static boolean isRepoEmpty(Git git) { | ||
// back-compat: the JGit "describe" command throws an exception in repositories with no commits, so call it | ||
// first to preserve this behavior in cases where this call would fail but native "git" call does not. | ||
try { | ||
new DescribeCommand(git.getRepository()).call() | ||
return true | ||
} catch (Exception ignored) { | ||
return false | ||
} | ||
} | ||
|
||
// getPeeledObjectId returns: | ||
// "if this ref is an annotated tag the id of the commit (or tree or blob) that the annotated tag refers to; | ||
// null if this ref does not refer to an annotated tag." | ||
// We use this to check if tag is annotated. | ||
static boolean isAnnotatedTag(Ref ref) { | ||
ObjectId peeledObjectId = ref.getPeeledObjectId() | ||
return peeledObjectId != null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
src/main/groovy/com/palantir/gradle/gitversion/JGitDescribe.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package com.palantir.gradle.gitversion | ||
|
||
import org.eclipse.jgit.api.Git | ||
import org.eclipse.jgit.internal.storage.file.FileRepository | ||
import org.eclipse.jgit.lib.Constants | ||
import org.eclipse.jgit.lib.ObjectId | ||
import org.eclipse.jgit.lib.Ref | ||
import org.eclipse.jgit.revwalk.RevCommit | ||
import org.eclipse.jgit.revwalk.RevWalk | ||
import org.slf4j.Logger | ||
import org.slf4j.LoggerFactory | ||
|
||
/** | ||
* JGit implementation of git describe with required flags. JGit support for describe is minimal and there is no support | ||
* for --first-parent behavior. | ||
*/ | ||
class JGitDescribe implements GitDescribe { | ||
private static final Logger log = LoggerFactory.getLogger(JGitDescribe.class) | ||
|
||
private File directory | ||
|
||
JGitDescribe(File directory) { | ||
this.directory = directory | ||
} | ||
|
||
@Override | ||
String describe(String prefix) { | ||
Git git = Git.wrap(new FileRepository(GitCli.getRootGitDir(directory))) | ||
if (!GitUtils.isRepoEmpty(git)) { | ||
log.debug("Repository is empty") | ||
return null | ||
} | ||
|
||
RevCommit headCommit | ||
RefWithTagNameComparator comparator | ||
try { | ||
ObjectId headObjectId = git.getRepository().resolve(Constants.HEAD) | ||
RevWalk walk = new RevWalk(git.getRepository()) | ||
headCommit = walk.parseCommit(headObjectId) | ||
comparator = new RefWithTagNameComparator(walk) | ||
} catch (Exception e) { | ||
log.debug("HEAD not found: {}", e) | ||
return null | ||
} | ||
|
||
try { | ||
List<String> revs = revList(headCommit) | ||
|
||
Map<String, RefWithTagName> commitHashToTag = mapCommitsToTags(git, comparator) | ||
|
||
// Walk back commit ancestors looking for tagged one | ||
for (int depth = 0; depth < revs.size(); depth++) { | ||
String rev = revs.get(depth) | ||
if (commitHashToTag.containsKey(rev)) { | ||
String exactTag = commitHashToTag.get(rev).getTag() | ||
// Mimics '--match=${prefix}*' flag in 'git describe --tags --exact-match' | ||
if (exactTag.startsWith(prefix)) { | ||
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(headCommit.toObjectId().getName()) | ||
} catch (Exception e) { | ||
log.debug("JGit describe failed with {}", e) | ||
return null | ||
} | ||
} | ||
|
||
// Mimics 'git rev-list --first-parent <commit>' | ||
private List<String> revList(RevCommit commit) { | ||
List<String> revs = new ArrayList<>() | ||
while (commit) { | ||
revs.add(commit.getName()) | ||
try { | ||
// There is no way to check if this exists without failing | ||
commit = commit.getParent(0) | ||
} catch (Exception ignored) { | ||
break | ||
} | ||
} | ||
return revs | ||
} | ||
|
||
// Maps all commits returned by 'git show-ref --tags -d' to output of 'git describe --tags --exact-match <commit>' | ||
private Map<String, RefWithTagName> mapCommitsToTags(Git git, RefWithTagNameComparator comparator) { | ||
// 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<String, RefWithTagName> commitHashToTag = new HashMap<>() | ||
for (Map.Entry<String, Ref> entry : git.getRepository().getTags()) { | ||
RefWithTagName refWithTagName = new RefWithTagName(entry.getValue(), entry.getKey()) | ||
updateCommitHashMap(commitHashToTag, comparator, entry.getValue().getObjectId(), refWithTagName) | ||
// Also add dereferenced commit hash if exists | ||
ObjectId peeledRef = refWithTagName.getRef().getPeeledObjectId() | ||
if (peeledRef) { | ||
updateCommitHashMap(commitHashToTag, comparator, peeledRef, refWithTagName) | ||
} | ||
} | ||
return commitHashToTag | ||
} | ||
|
||
private void updateCommitHashMap(Map<String, RefWithTagName> map, RefWithTagNameComparator comparator, | ||
ObjectId objectId, RefWithTagName ref) { | ||
// Smallest ref (ordered by this comparator) from list of refs is chosen for each commit. | ||
// This ensures we get same behavior as in 'git describe --tags --exact-match <commit>' | ||
String commitHash = objectId.getName() | ||
if (map.containsKey(commitHash)) { | ||
if (comparator.compare(ref, map.get(commitHash)) < 0) { | ||
map.put(commitHash, ref) | ||
} | ||
} else { | ||
map.put(commitHash, ref) | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
src/main/groovy/com/palantir/gradle/gitversion/NativeGitDescribe.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
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.eclipse.jgit.api.Git | ||
import org.eclipse.jgit.internal.storage.file.FileRepository | ||
import org.slf4j.Logger | ||
import org.slf4j.LoggerFactory | ||
|
||
/** | ||
* 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 File directory | ||
|
||
NativeGitDescribe(File directory) { | ||
this.directory = directory | ||
} | ||
|
||
@Override | ||
String describe(String prefix) { | ||
if (!gitCommandExists()) { | ||
return null | ||
} | ||
|
||
def runGitCmd = { String... commands -> | ||
return GitCli.runGitCommand(directory, commands) | ||
} | ||
|
||
Git git = Git.wrap(new FileRepository(GitCli.getRootGitDir(directory))) | ||
if (!GitUtils.isRepoEmpty(git)) { | ||
log.debug("Repository is empty") | ||
return null | ||
} | ||
|
||
try { | ||
// Get SHAs of all tags, we only need to search for these later on | ||
Set<String> tagRefs = Sets.newHashSet() | ||
for (String tag : LINE_SPLITTER.splitToList(runGitCmd("show-ref", "--tags", "-d"))) { | ||
List<String> 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<String> 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 != "") { | ||
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) | ||
GitCli.verifyGitCommandExists() | ||
return true | ||
} catch (Exception e) { | ||
log.debug("Native git command not found: {}", e) | ||
return false | ||
} | ||
} | ||
} |
Oops, something went wrong.