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 4a03801dbeb4d999a915b0af8b1b69a550bebdb4..0de27619547e923f5c4bc7a9624c874fac05cadf 100644 --- a/src/de/uni_passau/fim/seibt/gitwrapper/repo/Repository.java +++ b/src/de/uni_passau/fim/seibt/gitwrapper/repo/Repository.java @@ -22,9 +22,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import org.apache.commons.io.FileUtils; - import de.uni_passau.fim.seibt.gitwrapper.process.ProcessExecutor.ExecRes; +import org.apache.commons.io.FileUtils; /** * A git {@link Repository}. @@ -53,6 +52,10 @@ public class Repository { private static final String CONFLICT_MIDDLE = "======="; private static final String CONFLICT_END = ">>>>>>>"; + // The prefixes of the full symbolic names of (remote) branches. + private static final String FULL_SYMBOLIC_BRANCH = "refs/heads/"; + private static final String FULL_SYMBOLIC_REMOTE_BRANCH = "refs/remotes/"; + private GitWrapper git; private String url; @@ -234,43 +237,11 @@ public class Repository { return Optional.of(commits.get(id)); } - if (!isCommit(id)) { - return Optional.empty(); - } - - return toHash(id).map(sha1 -> commits.computeIfAbsent(sha1, fullID -> new Commit(this, fullID))); + return verify(false, id, TYPE_COMMIT).map(sha1 -> commits.computeIfAbsent(sha1, fullID -> new Commit(this, fullID))); } /** - * Determines whether the given object ID designates a commit. - * - * @param id - * the ID to check - * @return true if 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.getStdOutTrimmed().startsWith(TYPE_COMMIT); - - if (isCommit) { - LOG.finer(() -> String.format("%s is a commit.", id)); - } - - return isCommit; - }; - - return catFile.map(toBoolean).orElse(false); - } - - /** - * Optionally returns a {@link Branch} for the given name. If the given name does not designated a branch that + * Optionally returns a {@link Branch} for the given name. If the given name does not designate a branch that * exists in this {@link Repository}, an empty {@link Optional} will be returned. * * @param name @@ -282,39 +253,27 @@ public class Repository { return Optional.of(branches.get(name)); } - if (!isBranch(name)) { - return Optional.empty(); - } - - return Optional.of(branches.computeIfAbsent(name, theName -> new Branch(this, theName))); - } - - /** - * Determines whether the given name designates a branch. - * - * @param name - * the branch name to check - * @return true if the ID designates a commit - */ - private boolean isBranch(String name) { - Optional<ExecRes> catFile = git.exec(dir, "branch", "--list", "--no-color", "--column=plain"); - Function<ExecRes, Boolean> toBoolean = res -> { - - if (git.failed(res)) { - LOG.warning(() -> String.format("Failed to determine whether %s is a valid branch id.", name)); + Function<String, Branch> toBranch = fullName -> { + if (fullName.isEmpty()) { + LOG.warning(() -> String.format("The name '%s' does not designate a branch in %s.", name, this)); return null; } - boolean isBranch = res.stdOut.contains(name); + String branchName; - if (isBranch) { - LOG.finer(() -> String.format("%s is a branch.", name)); + if (fullName.startsWith(FULL_SYMBOLIC_BRANCH)) { + branchName = fullName.substring(FULL_SYMBOLIC_BRANCH.length()); + } else if (fullName.startsWith(FULL_SYMBOLIC_REMOTE_BRANCH)) { + branchName = fullName.substring(FULL_SYMBOLIC_REMOTE_BRANCH.length()); + } else { + LOG.warning(() -> String.format("The name '%s' designates neither a local nor a remote branch in %s.", fullName, this)); + return null; } - return isBranch; + return branches.computeIfAbsent(branchName, newBranchName -> new Branch(this, newBranchName)); }; - return catFile.map(toBoolean).orElse(false); + return verify(true, name, null).map(toBranch); } /** @@ -322,20 +281,50 @@ public class Repository { * * @param id * the <code>id</code> to transform - * @return the full SHA1 hash or an empty {@link Optional} if an exception occurs + * @return the full SHA1 hash or an empty {@link Optional} if an exception occurs or {@code id} can not be converted + * to its associated git hash */ Optional<String> toHash(String id) { - Optional<ExecRes> revParse = git.exec(dir, "rev-parse", id); + return verify(false, id, null); + } + + /** + * Verifies that the given git-thing exists and can be resolved to a raw SHA1 hash. If type is not {@link null}, + * git will be asked to verify that the thing is also of the given type. If {@code fullSymbolicName} is set, the + * returned {@code String} will be (if present) the full symbolic name of the reference. If {@code arg} does + * not designate a reference but does exist in the repository, an empty {@code String} will be returned. This + * is the case (for example) for an existing commit in the repository. + * + * @param fullSymbolicName + * whether to return the full symbolic name of {@code arg} if possible + * @param arg + * the thing to check + * @param type + * the type of the thing, ignored if {@code null} + * @return optionally the full SHA1 hash (or full symbolic name) or an empty {@link Optional} if the thing can not + * be found or turned into a full hash + */ + private Optional<String> verify(boolean fullSymbolicName, String arg, String type) { + String argument = type != null ? String.format("%s^{%s}", arg, type) : arg; + Optional<ExecRes> revParse; + + if (fullSymbolicName) { + revParse = git.exec(dir, "rev-parse", "--symbolic-full-name", "--verify", argument); + } else { + revParse = git.exec(dir, "rev-parse", "--verify", argument); + } + Function<ExecRes, String> toHash = res -> { if (git.failed(res)) { - LOG.warning(() -> String.format("Failed to resolve %s to its unique SHA1 hash.", id)); + LOG.warning(() -> String.format("Failed to resolve '%s' to its unique SHA1 hash.", arg)); return null; } - LOG.finer(() -> String.format("Resolved %s to %s.", id, res.getStdOutTrimmed())); + String hash = res.getStdOutTrimmed(); + LOG.finer(() -> String.format("Verified that %s exists and resolved it to %s.", arg, hash)); - return res.getStdOutTrimmed(); + return hash; }; return revParse.map(toHash);