All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.project13.core.jgit.DescribeCommand Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of git-commit-id-plugin-core by Konrad 'ktoso' Malawski 
 *
 * git-commit-id-plugin-core is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * git-commit-id-plugin-core is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with git-commit-id-plugin-core.  If not, see .
 */

package pl.project13.core.jgit;

import org.eclipse.jgit.api.GitCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;

import pl.project13.core.git.GitDescribeConfig;
import pl.project13.core.log.LogInterface;
import pl.project13.core.util.Pair;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;

/**
 * Implements git's 
describe
command. */ public class DescribeCommand extends GitCommand { private LogInterface log; private JGitCommon jGitCommon; private String evaluateOnCommit; // TODO not yet implemented options: // private boolean containsFlag = false; // private boolean allFlag = false; // private boolean tagsFlag = false; // private Optional candidatesOption = Optional.of(10); // private boolean exactMatchFlag = false; private Optional matchOption = Optional.empty(); /** * How many chars of the commit hash should be displayed? 7 is the default used by git. */ private int abbrev = 7; /** * Skipping lightweight tags by default - that's how git-describe works by default. * {@link DescribeCommand#tags(Boolean)} for more details. */ private boolean tagsFlag = false; private boolean alwaysFlag = true; /** * Corresponds to
--long
. Always use the
TAG-N-HASH
format, even when ON a tag. */ private boolean forceLongFormat = false; /** * The string marker (such as "DEV") to be suffixed to the describe result when the working directory is dirty */ private Optional dirtyOption = Optional.empty(); /** * Creates a new describe command which interacts with a single repository * * @param evaluateOnCommit the commit that should be used as reference to generate the properties from * @param repo the {@link Repository} this command should interact with * @param log logger bridge to direct logs to * @return itself with the options set as specified by the arguments to allow fluent configuration */ @Nonnull public static DescribeCommand on(String evaluateOnCommit, Repository repo, LogInterface log) { return new DescribeCommand(evaluateOnCommit, repo, log); } /** * Creates a new describe command which interacts with a single repository * * @param evaluateOnCommit the commit that should be used as reference to generate the properties from * @param repo the {@link Repository} this command should interact with * @param log logger bridge to direct logs to * @return itself with the options set as specified by the arguments to allow fluent configuration */ private DescribeCommand(@Nonnull String evaluateOnCommit, @Nonnull Repository repo, @Nonnull LogInterface log) { super(repo); this.evaluateOnCommit = evaluateOnCommit; this.jGitCommon = new JGitCommon(log); this.log = log; } /** *
--always
* * Show uniquely abbreviated commit object as fallback. * *
true
by default. * * @param always set to `true` when you want the describe command show uniquely abbreviated commit object as fallback. * @return itself with the `--always` option set as specified by the argument to allow fluent configuration */ @Nonnull public DescribeCommand always(boolean always) { this.alwaysFlag = always; log.debug(String.format("--always = %s", always)); return this; } /** *
--long
* * Always output the long format (the tag, the number of commits and the abbreviated commit name) * even when it matches a tag. This is useful when you want to see parts of the commit object name * in "describe" output, even when the commit in question happens to be a tagged version. Instead * of just emitting the tag name, it will describe such a commit as v1.2-0-gdeadbee (0th commit * since tag v1.2 that points at object deadbee....). * *
false
by default. * * @param forceLongFormat set to `true` if you always want to output the long format * @return itself with the `--long` option set as specified by the argument to allow fluent configuration */ @Nonnull public DescribeCommand forceLongFormat(@Nullable Boolean forceLongFormat) { if (forceLongFormat != null && forceLongFormat) { this.forceLongFormat = true; log.debug(String.format("--long = %s", true)); } return this; } /** *
--abbrev=N
* * Instead of using the default 7 hexadecimal digits as the abbreviated object name, * use N digits, or as many digits as needed to form a unique object name. * * An `n` of 0 will suppress long format, only showing the closest tag. * * @param n the length of the abbreviated object name * @return itself with the `--abbrev` option set as specified by the argument to allow fluent configuration */ @Nonnull public DescribeCommand abbrev(@Nullable Integer n) { if (n != null) { if (n >= 41) { throw new IllegalArgumentException("N (commit abbrev length) must be < 41. (Was:[" + n + "])"); } if (n < 0) { throw new IllegalArgumentException("N (commit abbrev length) must be positive! (Was [" + n + "])"); } log.debug(String.format("--abbrev = %s", n)); abbrev = n; } return this; } /** *
--tags
*

* Instead of using only the annotated tags, use any tag found in .git/refs/tags. * This option enables matching a lightweight (non-annotated) tag. *

* *

Searching for lightweight tags is false by default.

* * Example: *
   *    b6a73ed - (HEAD, master)
   *    d37a598 - (v1.0-fixed-stuff) - a lightweight tag (with no message)
   *    9597545 - (v1.0) - an annotated tag
   *
   *  $ git describe
   *    annotated-tag-2-gb6a73ed     # the nearest "annotated" tag is found
   *
   *  $ git describe --tags
   *    lightweight-tag-1-gb6a73ed   # the nearest tag (including lightweights) is found
   * 
* *

* Using only annotated tags to mark builds may be useful if you're using tags to help yourself with annotating * things like "i'll get back to that" etc - you don't need such tags to be exposed. But if you want lightweight * tags to be included in the search, enable this option. *

* * @param includeLightweightTagsInSearch set to `true` if you want to matching a lightweight (non-annotated) tag * @return itself with the `--tags` option set as specified by the argument to allow fluent configuration */ @Nonnull public DescribeCommand tags(@Nullable Boolean includeLightweightTagsInSearch) { if (includeLightweightTagsInSearch != null && includeLightweightTagsInSearch) { tagsFlag = includeLightweightTagsInSearch; log.debug(String.format("--tags = %s", includeLightweightTagsInSearch)); } return this; } /** * Alias for {@link DescribeCommand#tags(Boolean)} with true value * @return itself with the `--tags` option set to `true` to allow fluent configuration */ public DescribeCommand tags() { return tags(true); } /** * Apply all configuration options passed in with `config`. * If a setting is null, it will not be applied - so for abbrev for example, the default 7 would be used. * * @param config A configuration that shall be applied to the current one * @return itself, after applying the settings */ @Nonnull public DescribeCommand apply(@Nullable GitDescribeConfig config) { if (config != null) { always(config.isAlways()); dirty(config.getDirty()); abbrev(config.getAbbrev()); forceLongFormat(config.getForceLongFormat()); tags(config.getTags()); match(config.getMatch()); } return this; } /** *
--dirty[=mark]
* Describe the working tree. It means describe HEAD and appends mark (
-dirty
by default) if the * working tree is dirty. * * @param dirtyMarker the marker name to be appended to the describe output when the workspace is dirty * @return itself with the `--dirty` option set as specified by the argument to allow fluent configuration */ @Nonnull public DescribeCommand dirty(@Nullable String dirtyMarker) { Optional option = Optional.ofNullable(dirtyMarker); log.debug(String.format("--dirty = %s", option.orElse(""))); this.dirtyOption = option; return this; } /** *
--match glob-pattern
* Consider only those tags which match the given glob pattern. * * @param pattern the glob style pattern to match against the tag names * @return itself with the `--match` option set as specified by the argument to allow fluent configuration */ @Nonnull public DescribeCommand match(@Nullable String pattern) { if (!"*".equals(pattern)) { matchOption = Optional.ofNullable(pattern); log.debug(String.format("--match = %s", matchOption.orElse(""))); } return this; } @Override public DescribeResult call() throws GitAPIException { // needed for abbrev id's calculation ObjectReader objectReader = repo.newObjectReader(); // get tags String matchPattern = createMatchPattern(); Map> tagObjectIdToName = jGitCommon.findTagObjectIds(repo, tagsFlag, matchPattern); // get current commit RevCommit evalCommit = findEvalCommitObjectId(evaluateOnCommit, repo); ObjectId evalCommitId = evalCommit.getId(); // check if dirty boolean dirty = findDirtyState(repo); if (hasTags(evalCommit, tagObjectIdToName) && !forceLongFormat) { String tagName = tagObjectIdToName.get(evalCommit).iterator().next(); log.info(String.format("The commit we're on is a Tag ([%s]) and forceLongFormat == false, returning.", tagName)); return new DescribeResult(tagName, dirty, dirtyOption); } // get commits, up until the nearest tag List commits; try { commits = jGitCommon.findCommitsUntilSomeTag(repo, evalCommit, tagObjectIdToName); } catch (Exception e) { if (alwaysFlag) { // Show uniquely abbreviated commit object as fallback commits = Collections.emptyList(); } else { throw e; } } // if there is no tags or any tag is not on that branch then return generic describe if (foundZeroTags(tagObjectIdToName) || commits.isEmpty()) { return new DescribeResult(objectReader, evalCommitId, dirty, dirtyOption) .withCommitIdAbbrev(abbrev); } // check how far away from a tag we are int distance = jGitCommon.distanceBetween(repo, evalCommit, commits.get(0)); String tagName = tagObjectIdToName.get(commits.get(0)).iterator().next(); Pair howFarFromWhichTag = Pair.of(distance, tagName); // if it's null, no tag's were found etc, so let's return just the commit-id return createDescribeResult(objectReader, evalCommitId, dirty, howFarFromWhichTag); } /** * Prepares the final result of this command. * It tries to put as much information as possible into the result, * and will fallback to a plain commit hash if nothing better is returnable. * * The exact logic is following what
git-describe
would do. * * @param objectReader A reader to read objects from {@link Repository#getObjectDatabase()}. * @param headCommitId An unique hash of the head-commit * @param dirty An indication if the current repository is considered
dirty
* @param howFarFromWhichTag A Pair that consists of a string and an integer. The String represents the closest Tag and the integer the amount of commits that have been preformed since then * @return The result of a git describe command with the specified settings. */ private DescribeResult createDescribeResult(ObjectReader objectReader, ObjectId headCommitId, boolean dirty, @Nullable Pair howFarFromWhichTag) { if (howFarFromWhichTag == null) { return new DescribeResult(objectReader, headCommitId, dirty, dirtyOption) .withCommitIdAbbrev(abbrev); } else if (howFarFromWhichTag.first > 0 || forceLongFormat) { return new DescribeResult(objectReader, howFarFromWhichTag.second, howFarFromWhichTag.first, headCommitId, dirty, dirtyOption, forceLongFormat) .withCommitIdAbbrev(abbrev); // we're a bit away from a tag } else if (howFarFromWhichTag.first == 0) { return new DescribeResult(howFarFromWhichTag.second) .withCommitIdAbbrev(abbrev); // we're ON a tag } else if (alwaysFlag) { return new DescribeResult(objectReader, headCommitId) .withCommitIdAbbrev(abbrev); // we have no tags! display the commit } else { return DescribeResult.EMPTY; } } private static boolean foundZeroTags(@Nonnull Map> tags) { return tags.isEmpty(); } // Visible for testing boolean findDirtyState(Repository repo) throws GitAPIException { return JGitCommon.isRepositoryInDirtyState(repo); } // Visible for testing static boolean hasTags(ObjectId headCommit, @Nonnull Map> tagObjectIdToName) { return tagObjectIdToName.containsKey(headCommit); } RevCommit findEvalCommitObjectId(@Nonnull String evaluateOnCommit, @Nonnull Repository repo) throws RuntimeException { return jGitCommon.findEvalCommitObjectId(evaluateOnCommit, repo); } private String createMatchPattern() { if (!matchOption.isPresent()) { return ".*"; } return jGitCommon.createMatchPattern(matchOption.get()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy