diff --git a/src/de/uni_passau/fim/seibt/gitwrapper/repo/Commit.java b/src/de/uni_passau/fim/seibt/gitwrapper/repo/Commit.java index ffb2a39c58ef5f5f67a13c3f4630d99d1a58e95b..7d240900848ebded71ece2ead38d492db4464e44 100644 --- a/src/de/uni_passau/fim/seibt/gitwrapper/repo/Commit.java +++ b/src/de/uni_passau/fim/seibt/gitwrapper/repo/Commit.java @@ -50,7 +50,7 @@ public class Commit { LOG.fine(() -> String.format("Found %d parents for %s.", ids.length - 1, this)); LOG.finer(() -> String.format("Commit id and parents are:%n%s", String.join(System.lineSeparator(), id))); - return Arrays.stream(ids).skip(1).map(repo::getCommit).collect(Collectors.toList()); + return Arrays.stream(ids).skip(1).map(repo::getCommitUnchecked).collect(Collectors.toList()); }; return revList.map(toParentsList).orElse(Collections.emptyList()); @@ -83,7 +83,7 @@ public class Commit { return null; } - Commit base = repo.getCommit(res.output.trim()); + Commit base = repo.getCommitUnchecked(res.output.trim()); LOG.fine(() -> String.format("Commits %s and %s have the merge base %s.", this, other, base)); diff --git a/src/de/uni_passau/fim/seibt/gitwrapper/repo/Repository.java b/src/de/uni_passau/fim/seibt/gitwrapper/repo/Repository.java index ebc9760587145e017c092eccff7053903de4e809..4d103af3c80246d750088f84cd14556b39ff473c 100644 --- a/src/de/uni_passau/fim/seibt/gitwrapper/repo/Repository.java +++ b/src/de/uni_passau/fim/seibt/gitwrapper/repo/Repository.java @@ -23,6 +23,8 @@ public class Repository { private static final Logger LOG = Logger.getLogger(Repository.class.getCanonicalName()); + private static final String TYPE_COMMIT = "commit"; + private GitWrapper git; private String url; @@ -73,23 +75,95 @@ public class Repository { LOG.fine(() -> String.format("Found %d merge commits in %s.", lines.length, this)); LOG.finer(() -> String.format("Merge commits are:%n%s", String.join(System.lineSeparator(), lines))); - return Arrays.stream(lines).map(this::getCommit).collect(Collectors.toList()); + return Arrays.stream(lines).map(this::getCommitUnchecked).collect(Collectors.toList()); }; return revList.map(toCommitList).orElse(Collections.emptyList()); } /** - * Returns a {@link Commit} for the given id. The <code>id</code> is not checked for validity, this method - * only ensures that only one {@link Commit} object is created for every <code>id</code>. + * Returns a {@link Commit} for the given ID. The caller must ensure that the ID is a full SHA1 hash of a + * commit that exists in this repository. * - * @param id the ID of the commit + * @param id the ID for the {@link Commit} * @return the {@link Commit} */ - public Commit getCommit(String id) { + Commit getCommitUnchecked(String id) { return commits.computeIfAbsent(id, theID -> new Commit(this, theID)); } + /** + * Returns a {@link Commit} for the given ID. If the given ID does not designate a commit that exists in this + * {@link Repository} an empty {@link Optional} will be returned. The ID will be resolved to a full SHA1 hash. + * + * @param id the ID of the commit + * @return the {@link Commit} or an empty {@link Optional} if the ID is invalid or an exception occurs + */ + public Optional<Commit> getCommit(String id) { + + if (commits.containsKey(id)) { + return Optional.of(commits.get(id)); + } + + if (!isCommit(id)) { + return Optional.empty(); + } + + return toHash(id).map(fullID -> commits.put(fullID, new Commit(this, fullID))); + } + + /** + * Determines whether the given object ID designates a commit. + * + * @param id + * the ID to check + * @return true iff the ID designates a commit + */ + private boolean isCommit(String id) { + Optional<ExecRes> catFile = git.exec(dir, "cat-file", "-t", id); + Function<ExecRes, Boolean> toBoolean = res -> { + + if (git.failed(res)) { + LOG.warning(() -> String.format("Failed to determine whether %s is a valid commit id.", id)); + return null; + } + + boolean isCommit = res.output.startsWith(TYPE_COMMIT); + + if (isCommit) { + LOG.finer(() -> String.format("%s is a commit.", id)); + } + + return isCommit; + }; + + return catFile.map(toBoolean).orElse(false); + } + + /** + * Resolves the given <code>id</code> to the full SHA1 hash. + * + * @param id + * the <code>id</code> to transform + * @return the full SHA1 hash or an empty {@link Optional} if an exception occurs + */ + private Optional<String> toHash(String id) { + Optional<ExecRes> revParse = git.exec(dir, "rev-parse", id); + Function<ExecRes, String> toHash = res -> { + + if (git.failed(res)) { + LOG.warning(() -> String.format("Failed to resolve %s to its unique SHA1 hash.", id)); + return null; + } + + LOG.finer(() -> String.format("Resolved %s to %s.", id, res.output)); + + return res.output; + }; + + return revParse.map(toHash); + } + /** * Returns the {@link GitWrapper} used by this {@link Repository}. *