-
Notifications
You must be signed in to change notification settings - Fork 76
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
- Loading branch information
Showing
9 changed files
with
449 additions
and
65 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 (Throwable 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
123 changes: 123 additions & 0 deletions
123
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,123 @@ | ||
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 | ||
} | ||
|
||
ObjectId headObjectId | ||
RevCommit headCommit | ||
RevWalk walk | ||
try { | ||
headObjectId = git.getRepository().resolve(Constants.HEAD) | ||
walk = new RevWalk(git.getRepository()) | ||
headCommit = walk.parseCommit(headObjectId) | ||
} catch (Throwable ignored) { | ||
log.debug("HEAD not found") | ||
return null | ||
} | ||
|
||
try { | ||
List<String> revs = revList(headCommit) | ||
|
||
Map<String, String> commitHashToTag = mapCommitsToTags(git, walk) | ||
|
||
// 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) | ||
// 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(headObjectId.getName()) | ||
} catch (Throwable t) { | ||
log.debug("JGit describe failed with {}", t) | ||
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 { | ||
commit = commit.getParent(0) | ||
} catch (Throwable 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, String> mapCommitsToTags(Git git, RevWalk walk) { | ||
// 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, List<RefWithTagName>> commitHashToTags = new HashMap<>() | ||
for (Map.Entry<String, Ref> entry : git.getRepository().getTags()) { | ||
RefWithTagName refWithTagName = new RefWithTagName(entry.getValue(), entry.getKey()) | ||
addRefToCommitHashMap(commitHashToTags, entry.getValue().getObjectId(), refWithTagName) | ||
// Also add dereferenced commit hash if exists | ||
ObjectId peeledRef = refWithTagName.getRef().getPeeledObjectId() | ||
if (peeledRef) { | ||
addRefToCommitHashMap(commitHashToTags, peeledRef, refWithTagName) | ||
} | ||
} | ||
|
||
// 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>' | ||
RefWithTagNameComparator comparator = new RefWithTagNameComparator(walk) | ||
|
||
// Map from commit hash to chosen tag | ||
Map<String, String> commitHashToTag = new HashMap<>() | ||
for (Map.Entry<String, List<RefWithTagName>> entry : commitHashToTags) { | ||
RefWithTagName refWithTagName = entry.getValue().min(comparator) | ||
commitHashToTag.put(entry.getKey(), refWithTagName.getTag()) | ||
} | ||
|
||
return commitHashToTag | ||
} | ||
|
||
private void addRefToCommitHashMap(Map<String, List<RefWithTagName>> map, ObjectId objectId, RefWithTagName ref) { | ||
String commitHash = objectId.getName() | ||
if (map.containsKey(commitHash)) { | ||
map.get(commitHash).add(ref) | ||
} else { | ||
map.put(commitHash, new ArrayList<RefWithTagName>([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 (Throwable t) { | ||
log.debug("Native git describe failed: {}", t) | ||
return null | ||
} | ||
} | ||
|
||
private boolean gitCommandExists() { | ||
try { | ||
// verify that "git" command exists (throws exception if it does not) | ||
GitCli.verifyGitCommandExists() | ||
return true | ||
} catch (Throwable t) { | ||
log.debug("Native git command not found: {}", t) | ||
return false | ||
} | ||
} | ||
} |
Oops, something went wrong.