diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/lib')
66 files changed, 2724 insertions, 944 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java index 9109cfd769..c3dffdfe79 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java @@ -19,7 +19,7 @@ import org.eclipse.jgit.internal.JGitText; /** * Git configuration option <a - * href=https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev"> + * href="https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev"> * core.abbrev</a> * * @since 6.1 @@ -100,6 +100,7 @@ public final class AbbrevConfig { * object names to stay unique for some time. * * @param repo + * the repository the AbbrevConfig shall be computed for * @return appropriate value computed based on the approximate number of * packed objects in a repository */ @@ -128,7 +129,10 @@ public final class AbbrevConfig { private int abbrev; /** + * Create an {@code AbbrevConfig} + * * @param abbrev + * abbreviation length */ private AbbrevConfig(int abbrev) { this.abbrev = capAbbrev(abbrev); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java index dc5e5cc20f..a13136b9f1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java @@ -321,13 +321,11 @@ public final class AbbreviatedObjectId implements Serializable { return mask(nibbles, word, v); } - /** {@inheritDoc} */ @Override public int hashCode() { return w1; } - /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (o instanceof AbbreviatedObjectId) { @@ -366,7 +364,6 @@ public final class AbbreviatedObjectId implements Serializable { return new String(b, 0, nibbles); } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java index 7685b30f8e..f742e993a0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java @@ -35,23 +35,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> { * @param secondObjectId * the second identifier to compare. Must not be null. * @return true if the two identifiers are the same. - * @deprecated use {@link #isEqual(AnyObjectId, AnyObjectId)} instead - */ - @Deprecated - @SuppressWarnings("AmbiguousMethodReference") - public static boolean equals(final AnyObjectId firstObjectId, - final AnyObjectId secondObjectId) { - return isEqual(firstObjectId, secondObjectId); - } - - /** - * Compare two object identifier byte sequences for equality. - * - * @param firstObjectId - * the first identifier to compare. Must not be null. - * @param secondObjectId - * the second identifier to compare. Must not be null. - * @return true if the two identifiers are the same. * @since 5.4 */ public static boolean isEqual(final AnyObjectId firstObjectId, @@ -248,7 +231,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> { return abbr.prefixCompare(this) == 0; } - /** {@inheritDoc} */ @Override public final int hashCode() { return w2; @@ -266,7 +248,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> { return other != null ? isEqual(this, other) : false; } - /** {@inheritDoc} */ @Override public final boolean equals(Object o) { if (o instanceof AnyObjectId) { @@ -477,7 +458,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> { dst[o--] = '0'; } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java index b2242a11ca..0c1da83dfb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -13,19 +13,23 @@ package org.eclipse.jgit.lib; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BARE; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE; +import static org.eclipse.jgit.lib.Constants.CONFIG; import static org.eclipse.jgit.lib.Constants.DOT_GIT; +import static org.eclipse.jgit.lib.Constants.GITDIR_FILE; import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY; import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY; +import static org.eclipse.jgit.lib.Constants.GIT_COMMON_DIR_KEY; import static org.eclipse.jgit.lib.Constants.GIT_DIR_KEY; import static org.eclipse.jgit.lib.Constants.GIT_INDEX_FILE_KEY; import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY; import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY; +import static org.eclipse.jgit.lib.Constants.OBJECTS; import java.io.File; import java.io.IOException; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import org.eclipse.jgit.annotations.NonNull; @@ -70,7 +74,21 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re && ref[7] == ' '; } - private static File getSymRef(File workTree, File dotGit, FS fs) + /** + * Read symbolic reference file + * + * @param workTree + * the work tree path + * @param dotGit + * the .git file + * @param fs + * th FS util + * @return the file read from symbolic reference file + * @throws java.io.IOException + * the dotGit file is invalid reference + * @since 7.0 + */ + static File getSymRef(File workTree, File dotGit, FS fs) throws IOException { byte[] content = IO.readFully(dotGit); if (!isSymRef(content)) { @@ -102,6 +120,8 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re private File gitDir; + private File gitCommonDir; + private File objectDirectory; private List<File> alternateObjectDirectories; @@ -172,6 +192,30 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re } /** + * Set common dir. + * + * @param gitCommonDir + * {@code GIT_COMMON_DIR}, the common repository meta directory. + * @return {@code this} (for chaining calls). + * @since 7.0 + */ + public B setGitCommonDir(File gitCommonDir) { + this.gitCommonDir = gitCommonDir; + this.config = null; + return self(); + } + + /** + * Get common dir. + * + * @return common dir; null if not set. + * @since 7.0 + */ + public File getGitCommonDir() { + return gitCommonDir; + } + + /** * Set the directory storing the repository's objects. * * @param objectDirectory @@ -205,8 +249,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re */ public B addAlternateObjectDirectory(File other) { if (other != null) { - if (alternateObjectDirectories == null) - alternateObjectDirectories = new LinkedList<>(); + if (alternateObjectDirectories == null) { + alternateObjectDirectories = new ArrayList<>(); + } alternateObjectDirectories.add(other); } return self(); @@ -395,9 +440,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re * Read standard Git environment variables and configure from those. * <p> * This method tries to read the standard Git environment variables, such as - * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder - * instance. If an environment variable is set, it overrides the value - * already set in this builder. + * {@code GIT_DIR}, {@code GIT_COMMON_DIR}, {@code GIT_WORK_TREE} etc. to + * configure this builder instance. If an environment variable is set, it + * overrides the value already set in this builder. * * @return {@code this} (for chaining calls). */ @@ -409,9 +454,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re * Read standard Git environment variables and configure from those. * <p> * This method tries to read the standard Git environment variables, such as - * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder - * instance. If a property is already set in the builder, the environment - * variable is not used. + * {@code GIT_DIR}, {@code GIT_COMMON_DIR}, {@code GIT_WORK_TREE} etc. to + * configure this builder instance. If a property is already set in the + * builder, the environment variable is not used. * * @param sr * the SystemReader abstraction to access the environment. @@ -424,6 +469,13 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re setGitDir(new File(val)); } + if (getGitCommonDir() == null) { + String val = sr.getenv(GIT_COMMON_DIR_KEY); + if (val != null) { + setGitCommonDir(new File(val)); + } + } + if (getObjectDirectory() == null) { String val = sr.getenv(GIT_OBJECT_DIRECTORY_KEY); if (val != null) @@ -433,7 +485,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re if (getAlternateObjectDirectories() == null) { String val = sr.getenv(GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY); if (val != null) { - for (String path : val.split(File.pathSeparator)) + for (String path : val.split(File.pathSeparator, -1)) addAlternateObjectDirectory(new File(path)); } } @@ -453,7 +505,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re if (ceilingDirectories == null) { String val = sr.getenv(GIT_CEILING_DIRECTORIES_KEY); if (val != null) { - for (String path : val.split(File.pathSeparator)) + for (String path : val.split(File.pathSeparator, -1)) addCeilingDirectory(new File(path)); } } @@ -473,8 +525,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re */ public B addCeilingDirectory(File root) { if (root != null) { - if (ceilingDirectories == null) - ceilingDirectories = new LinkedList<>(); + if (ceilingDirectories == null) { + ceilingDirectories = new ArrayList<>(); + } ceilingDirectories.add(root); } return self(); @@ -599,6 +652,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re public B setup() throws IllegalArgumentException, IOException { requireGitDirOrWorkTree(); setupGitDir(); + setupCommonDir(); setupWorkTree(); setupInternals(); return self(); @@ -656,6 +710,20 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re } /** + * Perform standard common dir initialization. + * + * @throws java.io.IOException + * the repository could not be accessed + * @since 7.0 + */ + protected void setupCommonDir() throws IOException { + // no gitCommonDir? Try to get it from gitDir + if (getGitCommonDir() == null) { + setGitCommonDir(safeFS().getCommonDir(getGitDir())); + } + } + + /** * Perform standard work-tree initialization. * <p> * This is a method typically invoked inside of {@link #setup()}, near the @@ -693,8 +761,12 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re * the repository could not be accessed */ protected void setupInternals() throws IOException { - if (getObjectDirectory() == null && getGitDir() != null) - setObjectDirectory(safeFS().resolve(getGitDir(), Constants.OBJECTS)); + if (getObjectDirectory() == null) { + File commonDir = getGitCommonDir(); + if (commonDir != null) { + setObjectDirectory(safeFS().resolve(commonDir, OBJECTS)); + } + } } /** @@ -721,12 +793,13 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re * the configuration is not available. */ protected Config loadConfig() throws IOException { - if (getGitDir() != null) { + File commonDir = getGitCommonDir(); + if (commonDir != null) { // We only want the repository's configuration file, and not // the user file, as these parameters must be unique to this // repository and not inherited from other files. // - File path = safeFS().resolve(getGitDir(), Constants.CONFIG); + File path = safeFS().resolve(commonDir, CONFIG); FileBasedConfig cfg = new FileBasedConfig(path, safeFS()); try { cfg.load(); @@ -747,8 +820,29 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re // String path = cfg.getString(CONFIG_CORE_SECTION, null, CONFIG_KEY_WORKTREE); - if (path != null) + if (path != null) { return safeFS().resolve(getGitDir(), path).getCanonicalFile(); + } + + /* + * We are in worktree's $GIT_DIR folder + * ".git/worktrees/<worktree-name>" and want to get the working + * tree (checkout) path; so here we have an opposite link in file + * "gitdir" showing to the ".git" file located in the working tree read + * it and convert it to absolute path if it's relative + */ + File gitDirFile = new File(getGitDir(), GITDIR_FILE); + if (gitDirFile.isFile()) { + String workDirPath = new String(IO.readFully(gitDirFile)).trim(); + File workTreeDotGitFile = new File(workDirPath); + if (!workTreeDotGitFile.isAbsolute()) { + workTreeDotGitFile = new File(getGitDir(), workDirPath) + .getCanonicalFile(); + } + if (workTreeDotGitFile != null) { + return workTreeDotGitFile.getParentFile(); + } + } // If core.bare is set, honor its value. Assume workTree is // the parent directory of the repository. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java index d2367cc3d5..f9952501c3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java @@ -716,7 +716,6 @@ public class BatchRefUpdate { : isForceRefLog(); } - /** {@inheritDoc} */ @Override public String toString() { StringBuilder r = new StringBuilder(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java index f826057370..8e92f20cb0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java @@ -47,13 +47,11 @@ public abstract class BatchingProgressMonitor implements ProgressMonitor { delayStartUnit = unit; } - /** {@inheritDoc} */ @Override public void start(int totalTasks) { // Ignore the number of tasks. } - /** {@inheritDoc} */ @Override public void beginTask(String title, int work) { endTask(); @@ -62,14 +60,12 @@ public abstract class BatchingProgressMonitor implements ProgressMonitor { task.delay(delayStartTime, delayStartUnit); } - /** {@inheritDoc} */ @Override public void update(int completed) { if (task != null) task.update(this, completed); } - /** {@inheritDoc} */ @Override public void endTask() { if (task != null) { @@ -78,7 +74,6 @@ public abstract class BatchingProgressMonitor implements ProgressMonitor { } } - /** {@inheritDoc} */ @Override public boolean isCancelled() { return false; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java index b11b230a8f..acaa6335d4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java @@ -40,6 +40,58 @@ public interface BitmapIndex { BitmapBuilder newBitmapBuilder(); /** + * Report the results of {@link #getBitmap(AnyObjectId)} + * + * @since 6.8 + */ + interface BitmapLookupListener { + + /** + * This object has a bitmap in the index + * + * @param oid + * object id + */ + void onBitmapFound(AnyObjectId oid); + + /** + * This object does not have a bitmap in the index + * + * @param oid + * object id + */ + void onBitmapNotFound(AnyObjectId oid); + + /** + * No-op instance + */ + BitmapLookupListener NOOP = new BitmapLookupListener() { + @Override + public void onBitmapFound(AnyObjectId oid) { + // Nothing to do + } + + @Override + public void onBitmapNotFound(AnyObjectId oid) { + // Nothing to do + } + }; + } + + /** + * Report to this listener whether {@link #getBitmap(AnyObjectId)} finds a + * commit. + * + * @param listener + * instance listening to lookup events in the index. Never null. + * Set to {@link BitmapLookupListener#NOOP} to disable. + * @since 6.8 + */ + default void addBitmapLookupListener(BitmapLookupListener listener) { + // Empty implementation for API compatibility + } + + /** * A bitmap representation of ObjectIds that can be iterated to return the * underlying {@code ObjectId}s or operated on with other {@code Bitmap}s. */ @@ -159,7 +211,11 @@ public interface BitmapIndex { @Override BitmapBuilder xor(Bitmap other); - /** @return the fully built immutable bitmap */ + /** + * Build the bitmap + * + * @return the fully built immutable bitmap + */ Bitmap build(); /** @@ -174,7 +230,11 @@ public interface BitmapIndex { */ boolean removeAllOrNone(PackBitmapIndex bitmapIndex); - /** @return the number of elements in the bitmap. */ + /** + * Get number of elements in the bitmap + * + * @return the number of elements in the bitmap. + */ int cardinality(); /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java index e15c7af932..7921052aaa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java @@ -187,8 +187,7 @@ public class BranchConfig { * @since 4.5 */ public BranchRebaseMode getRebaseMode() { - return config.getEnum(BranchRebaseMode.values(), - ConfigConstants.CONFIG_BRANCH_SECTION, branchName, + return config.getEnum(ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java index 6c625bb73a..39fc566034 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java @@ -33,6 +33,7 @@ public class BranchTrackingStatus { * the local branch * @return the tracking status, or null if it is not known * @throws java.io.IOException + * if an IO error occurred */ public static BranchTrackingStatus of(Repository repository, String branchName) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java index 1665f051e9..ad3c2c091d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java @@ -194,19 +194,6 @@ public class CommitBuilder extends ObjectBuilder { } } - /** - * Set the encoding for the commit information. - * - * @param encodingName - * the encoding name. See - * {@link java.nio.charset.Charset#forName(String)}. - * @deprecated use {@link #setEncoding(Charset)} instead. - */ - @Deprecated - public void setEncoding(String encodingName) { - setEncoding(Charset.forName(encodingName)); - } - @Override public byte[] build() throws UnsupportedEncodingException { ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -275,7 +262,6 @@ public class CommitBuilder extends ObjectBuilder { return build(); } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java index 6a9b45b065..b1ba5dfa28 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java @@ -119,7 +119,7 @@ public class CommitConfig { if (!StringUtils.isEmptyOrNull(comment)) { if ("auto".equalsIgnoreCase(comment)) { //$NON-NLS-1$ autoCommentChar = true; - } else { + } else if (comment != null) { char first = comment.charAt(0); if (first > ' ' && first < 127) { commentCharacter = first; @@ -403,7 +403,7 @@ public class CommitConfig { for (int i = 0; i < len; i++) { char ch = line.charAt(i); if (!Character.isWhitespace(ch)) { - if (ch >= 0 && ch < inUse.length) { + if (ch < inUse.length) { inUse[ch] = true; } break; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index d1d66d280e..345cb22f80 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -30,9 +30,11 @@ import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.events.ConfigChangedEvent; import org.eclipse.jgit.events.ConfigChangedListener; @@ -253,9 +255,8 @@ public class Config { * default value to return if no value was present. * @return an integer value from the configuration, or defaultValue. */ - public int getInt(final String section, final String name, - final int defaultValue) { - return typedGetter.getInt(this, section, null, name, defaultValue); + public int getInt(String section, String name, int defaultValue) { + return getInt(section, null, name, defaultValue); } /** @@ -263,6 +264,23 @@ public class Config { * * @param section * section the key is grouped within. + * @param name + * name of the key to get. + * @return an integer value from the configuration, or {@code null} if not + * set. + * @since 7.2 + */ + @Nullable + public Integer getInt(String section, String name) { + return getInt(section, null, name); + } + + + /** + * Obtain an integer value from the configuration. + * + * @param section + * section the key is grouped within. * @param subsection * subsection name, such a remote or branch name. * @param name @@ -271,10 +289,30 @@ public class Config { * default value to return if no value was present. * @return an integer value from the configuration, or defaultValue. */ - public int getInt(final String section, String subsection, - final String name, final int defaultValue) { + public int getInt(String section, String subsection, String name, + int defaultValue) { + Integer v = typedGetter.getInt(this, section, subsection, name, + Integer.valueOf(defaultValue)); + return v == null ? defaultValue : v.intValue(); + } + + /** + * Obtain an integer value from the configuration. + * + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @return an integer value from the configuration, or {@code null} if not + * set. + * @since 7.2 + */ + @Nullable + public Integer getInt(String section, String subsection, String name) { return typedGetter.getInt(this, section, subsection, name, - defaultValue); + null); } /** @@ -296,8 +334,30 @@ public class Config { */ public int getIntInRange(String section, String name, int minValue, int maxValue, int defaultValue) { - return typedGetter.getIntInRange(this, section, null, name, minValue, - maxValue, defaultValue); + return getIntInRange(section, null, name, + minValue, maxValue, defaultValue); + } + + /** + * Obtain an integer value from the configuration which must be inside given + * range. + * + * @param section + * section the key is grouped within. + * @param name + * name of the key to get. + * @param minValue + * minimum value + * @param maxValue + * maximum value + * @return an integer value from the configuration, or {@code null} if not + * set. + * @since 7.2 + */ + @Nullable + public Integer getIntInRange(String section, String name, int minValue, + int maxValue) { + return getIntInRange(section, null, name, minValue, maxValue); } /** @@ -321,8 +381,34 @@ public class Config { */ public int getIntInRange(String section, String subsection, String name, int minValue, int maxValue, int defaultValue) { + Integer v = typedGetter.getIntInRange(this, section, subsection, name, + minValue, maxValue, Integer.valueOf(defaultValue)); + return v == null ? defaultValue : v.intValue(); + } + + /** + * Obtain an integer value from the configuration which must be inside given + * range. + * + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param minValue + * minimum value + * @param maxValue + * maximum value + * @return an integer value from the configuration, or {@code null} if not + * set. + * @since 7.2 + */ + @Nullable + public Integer getIntInRange(String section, String subsection, String name, + int minValue, int maxValue) { return typedGetter.getIntInRange(this, section, subsection, name, - minValue, maxValue, defaultValue); + minValue, maxValue, null); } /** @@ -337,7 +423,23 @@ public class Config { * @return an integer value from the configuration, or defaultValue. */ public long getLong(String section, String name, long defaultValue) { - return typedGetter.getLong(this, section, null, name, defaultValue); + return getLong(section, null, name, defaultValue); + } + + /** + * Obtain an integer value from the configuration. + * + * @param section + * section the key is grouped within. + * @param name + * name of the key to get. + * @return an integer value from the configuration, or {@code null} if not + * set. + * @since 7.2 + */ + @Nullable + public Long getLong(String section, String name) { + return getLong(section, null, name); } /** @@ -354,9 +456,28 @@ public class Config { * @return an integer value from the configuration, or defaultValue. */ public long getLong(final String section, String subsection, - final String name, final long defaultValue) { - return typedGetter.getLong(this, section, subsection, name, - defaultValue); + String name, long defaultValue) { + Long v = typedGetter.getLong(this, section, subsection, name, + Long.valueOf(defaultValue)); + return v == null ? defaultValue : v.longValue(); + } + + /** + * Obtain an integer value from the configuration. + * + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @return an integer value from the configuration, or {@code null} if not + * set. + * @since 7.2 + */ + @Nullable + public Long getLong(String section, String subsection, String name) { + return typedGetter.getLong(this, section, subsection, name, null); } /** @@ -371,9 +492,26 @@ public class Config { * @return true if any value or defaultValue is true, false for missing or * explicit false */ - public boolean getBoolean(final String section, final String name, - final boolean defaultValue) { - return typedGetter.getBoolean(this, section, null, name, defaultValue); + public boolean getBoolean(String section, String name, + boolean defaultValue) { + Boolean v = typedGetter.getBoolean(this, section, null, name, + Boolean.valueOf(defaultValue)); + return v == null ? defaultValue : v.booleanValue(); + } + + /** + * Get a boolean value from the git config + * + * @param section + * section the key is grouped within. + * @param name + * name of the key to get. + * @return configured boolean value, or {@code null} if not set. + * @since 7.2 + */ + @Nullable + public Boolean getBoolean(String section, String name) { + return getBoolean(section, null, name); } /** @@ -390,15 +528,35 @@ public class Config { * @return true if any value or defaultValue is true, false for missing or * explicit false */ - public boolean getBoolean(final String section, String subsection, - final String name, final boolean defaultValue) { - return typedGetter.getBoolean(this, section, subsection, name, - defaultValue); + public boolean getBoolean(String section, String subsection, String name, + boolean defaultValue) { + Boolean v = typedGetter.getBoolean(this, section, subsection, name, + Boolean.valueOf(defaultValue)); + return v == null ? defaultValue : v.booleanValue(); + } + + /** + * Get a boolean value from the git config + * + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @return configured boolean value, or {@code null} if not set. + * @since 7.2 + */ + @Nullable + public Boolean getBoolean(String section, String subsection, String name) { + return typedGetter.getBoolean(this, section, subsection, name, null); } /** * Parse an enumeration from the configuration. * + * @param <T> + * type of the returned enum * @param section * section the key is grouped within. * @param subsection @@ -409,8 +567,8 @@ public class Config { * default value to return if no value was present. * @return the selected enumeration value, or {@code defaultValue}. */ - public <T extends Enum<?>> T getEnum(final String section, - final String subsection, final String name, final T defaultValue) { + public <T extends Enum<?>> T getEnum(String section, String subsection, + String name, @NonNull T defaultValue) { final T[] all = allValuesOf(defaultValue); return typedGetter.getEnum(this, all, section, subsection, name, defaultValue); @@ -431,6 +589,8 @@ public class Config { /** * Parse an enumeration from the configuration. * + * @param <T> + * type of the returned enum * @param all * all possible values in the enumeration which should be * recognized. Typically {@code EnumType.values()}. @@ -443,14 +603,41 @@ public class Config { * @param defaultValue * default value to return if no value was present. * @return the selected enumeration value, or {@code defaultValue}. + * @deprecated use {@link #getEnum(String, String, String, Enum)} or + * {{@link #getEnum(Enum[], String, String, String)}} instead. */ - public <T extends Enum<?>> T getEnum(final T[] all, final String section, - final String subsection, final String name, final T defaultValue) { + @Nullable + @Deprecated + public <T extends Enum<?>> T getEnum(T[] all, String section, + String subsection, String name, @Nullable T defaultValue) { return typedGetter.getEnum(this, all, section, subsection, name, defaultValue); } /** + * Parse an enumeration from the configuration. + * + * @param <T> + * type of the returned enum + * @param all + * all possible values in the enumeration which should be + * recognized. Typically {@code EnumType.values()}. + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @return the selected enumeration value, or {@code null} if not set. + * @since 7.2 + */ + @Nullable + public <T extends Enum<?>> T getEnum(T[] all, String section, + String subsection, String name) { + return typedGetter.getEnum(this, all, section, subsection, name, null); + } + + /** * Get string value or null if not found. * * @param section @@ -461,8 +648,8 @@ public class Config { * the key name * @return a String value from the config, <code>null</code> if not found */ - public String getString(final String section, String subsection, - final String name) { + @Nullable + public String getString(String section, String subsection, String name) { return getRawString(section, subsection, name); } @@ -521,8 +708,34 @@ public class Config { */ public long getTimeUnit(String section, String subsection, String name, long defaultValue, TimeUnit wantUnit) { + Long v = typedGetter.getTimeUnit(this, section, subsection, name, + Long.valueOf(defaultValue), wantUnit); + return v == null ? defaultValue : v.longValue(); + + } + + /** + * Parse a numerical time unit, such as "1 minute", from the configuration. + * + * @param section + * section the key is in. + * @param subsection + * subsection the key is in, or null if not in a subsection. + * @param name + * the key name. + * @param wantUnit + * the units of {@code defaultValue} and the return value, as + * well as the units to assume if the value does not contain an + * indication of the units. + * @return the value, or {@code null} if not set, expressed in + * {@code units}. + * @since 7.2 + */ + @Nullable + public Long getTimeUnit(String section, String subsection, String name, + TimeUnit wantUnit) { return typedGetter.getTimeUnit(this, section, subsection, name, - defaultValue, wantUnit); + null, wantUnit); } /** @@ -550,8 +763,9 @@ public class Config { * @return the {@link Path}, or {@code defaultValue} if not set * @since 5.10 */ + @Nullable public Path getPath(String section, String subsection, String name, - @NonNull FS fs, File resolveAgainst, Path defaultValue) { + @NonNull FS fs, File resolveAgainst, @Nullable Path defaultValue) { return typedGetter.getPath(this, section, subsection, name, fs, resolveAgainst, defaultValue); } @@ -722,7 +936,7 @@ public class Config { * responsible for issuing {@link #fireConfigChangedEvent()} calls * themselves. * - * @return <code></code> + * @return whether to issue change events for transient changes */ protected boolean notifyUponTransientChanges() { return true; @@ -735,7 +949,7 @@ public class Config { listeners.dispatch(new ConfigChangedEvent()); } - String getRawString(final String section, final String subsection, + private String getRawString(final String section, final String subsection, final String name) { String[] lst = getRawStringList(section, subsection, name); if (lst != null) { @@ -847,6 +1061,8 @@ public class Config { * name = value * </pre> * + * @param <T> + * type of the enum to set * @param section * section name, e.g "branch" * @param subsection @@ -915,29 +1131,52 @@ public class Config { * optional subsection value, e.g. a branch name */ public void unsetSection(String section, String subsection) { + removeSection(section, subsection); + } + + /** + * Removes all configuration values under a single section. + * + * @param section + * section name, e.g "branch" + * @param subsection + * optional subsection value, e.g. a branch name + * @return {@code true} if a section was present and was removed; + * {@code false} if the config was not changed (i.e., no such + * section was present) + * @since 6.8 + */ + public boolean removeSection(String section, String subsection) { ConfigSnapshot src, res; + AtomicBoolean changed = new AtomicBoolean(); do { src = state.get(); - res = unsetSection(src, section, subsection); + changed.set(false); + res = unsetSection(src, section, subsection, changed); } while (!state.compareAndSet(src, res)); + return changed.get(); } - private ConfigSnapshot unsetSection(final ConfigSnapshot srcState, - final String section, - final String subsection) { + private ConfigSnapshot unsetSection(ConfigSnapshot srcState, String section, + String subsection, AtomicBoolean changed) { final int max = srcState.entryList.size(); final ArrayList<ConfigLine> r = new ArrayList<>(max); boolean lastWasMatch = false; for (ConfigLine e : srcState.entryList) { - if (e.includedFrom == null && e.match(section, subsection)) { - // Skip this record, it's for the section we are removing. - lastWasMatch = true; + if (e.includedFrom != null) { + r.add(e); continue; } - - if (lastWasMatch && e.section == null && e.subsection == null) + if (lastWasMatch && e.section == null && e.subsection == null) { continue; // skip this padding line in the section. + } + lastWasMatch = e.match(section, subsection); + if (lastWasMatch) { + // Skip this record, it's for the section we are removing. + changed.set(true); + continue; + } r.add(e); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index c4b6bf955e..c4550329d3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -60,7 +60,8 @@ public final class ConfigConstants { public static final String CONFIG_KEY_PROMPT = "prompt"; /** - * The "trustExitCode" key within "difftool" or "mergetool.<name>." section + * The "trustExitCode" key within "difftool" or "mergetool.<name>." + * section * * @since 6.1 */ @@ -77,6 +78,13 @@ public final class ConfigConstants { public static final String CONFIG_DFS_SECTION = "dfs"; /** + * The dfs cache subsection prefix. + * + * @since 7.0 + */ + public static final String CONFIG_DFS_CACHE_PREFIX = "dfs."; + + /** * The "receive" section * @since 4.6 */ @@ -103,11 +111,18 @@ public final class ConfigConstants { /** The "gc" section */ public static final String CONFIG_GC_SECTION = "gc"; + /** + * The "repack" section + * @since 5.13.3 + */ + public static final String CONFIG_REPACK_SECTION = "repack"; + /** The "pack" section */ public static final String CONFIG_PACK_SECTION = "pack"; /** * The "fetch" section + * * @since 3.3 */ public static final String CONFIG_FETCH_SECTION = "fetch"; @@ -191,7 +206,36 @@ public final class ConfigConstants { public static final String CONFIG_KEY_SIGNINGKEY = "signingKey"; /** + * The "ssh" subsection key. + * + * @since 7.1 + */ + public static final String CONFIG_SSH_SUBSECTION = "ssh"; + + /** + * The "defaultKeyCommand" key. + * + * @since 7.1 + */ + public static final String CONFIG_KEY_SSH_DEFAULT_KEY_COMMAND = "defaultKeyCommand"; + + /** + * The "allowedSignersFile" key. + * + * @since 7.1 + */ + public static final String CONFIG_KEY_SSH_ALLOWED_SIGNERS_FILE = "allowedSignersFile"; + + /** + * The "revocationFile" key, + * + * @since 7.1 + */ + public static final String CONFIG_KEY_SSH_REVOCATION_FILE = "revocationFile"; + + /** * The "commit" section + * * @since 5.2 */ public static final String CONFIG_COMMIT_SECTION = "commit"; @@ -324,13 +368,24 @@ public final class ConfigConstants { public static final String CONFIG_KEY_DELTA_BASE_CACHE_LIMIT = "deltaBaseCacheLimit"; /** + * The "packExtensions" key + * + * @since 7.0 + **/ + public static final String CONFIG_KEY_PACK_EXTENSIONS = "packExtensions"; + + /** * The "symlinks" key * @since 3.3 */ public static final String CONFIG_KEY_SYMLINKS = "symlinks"; - /** The "streamFileThreshold" key */ - public static final String CONFIG_KEY_STREAM_FILE_TRESHOLD = "streamFileThreshold"; + /** + * The "streamFileThreshold" key + * + * @since 6.8 + */ + public static final String CONFIG_KEY_STREAM_FILE_THRESHOLD = "streamFileThreshold"; /** * The "packedGitMmap" key @@ -362,6 +417,12 @@ public final class ConfigConstants { */ public static final String CONFIG_KEY_PACKED_GIT_USE_STRONGREFS = "packedgitusestrongrefs"; + /** + * The "packedIndexGitUseStrongRefs" key + * @since 6.7 + */ + public static final String CONFIG_KEY_PACKED_INDEX_GIT_USE_STRONGREFS = "packedindexgitusestrongrefs"; + /** The "remote" key */ public static final String CONFIG_KEY_REMOTE = "remote"; @@ -385,6 +446,13 @@ public final class ConfigConstants { /** The "rebase" key */ public static final String CONFIG_KEY_REBASE = "rebase"; + /** + * The "checkout" key + * + * @since 7.2 + */ + public static final String CONFIG_KEY_CHECKOUT = "checkout"; + /** The "url" key */ public static final String CONFIG_KEY_URL = "url"; @@ -532,11 +600,21 @@ public final class ConfigConstants { /** * The "trustfolderstat" key in the "core" section + * * @since 3.6 + * @deprecated use {CONFIG_KEY_TRUST_STAT} instead */ + @Deprecated(since = "7.2", forRemoval = true) public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat"; /** + * The "trustfilestat" key in the "core"section + * + * @since 7.2 + */ + public static final String CONFIG_KEY_TRUST_STAT = "truststat"; + + /** * The "supportsAtomicFileCreation" key in the "core" section * * @since 4.5 @@ -727,6 +805,13 @@ public final class ConfigConstants { public static final String CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT = "bitmapexcessivebranchcount"; /** + * The "pack.bitmapExcessiveBranchTipCount" key + * + * @since 6.9 + */ + public static final String CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT = "bitmapexcessivebranchtipcount"; + + /** * The "pack.bitmapExcludedRefsPrefixes" key * @since 5.13.2 */ @@ -745,6 +830,13 @@ public final class ConfigConstants { public static final String CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT = "bitmaprecentcommitspan"; /** + * The "pack.writeReverseIndex" key + * + * @since 6.6 + */ + public static final String CONFIG_KEY_WRITE_REVERSE_INDEX = "writeReverseIndex"; + + /** * The "pack.buildBitmaps" key * @since 5.8 */ @@ -836,6 +928,13 @@ public final class ConfigConstants { public static final String CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX = "minBytesForObjSizeIndex"; /** + * The "repack.packKeptObjects" key + * + * @since 5.13.3 + */ + public static final String CONFIG_KEY_PACK_KEPT_OBJECTS = "packkeptobjects"; + + /** * The "feature" section * * @since 5.9 @@ -927,6 +1026,34 @@ public final class ConfigConstants { public static final String CONFIG_KEY_TRUST_PACKED_REFS_STAT = "trustPackedRefsStat"; /** + * The "trustLooseRefStat" key + * + * @since 6.9 + */ + public static final String CONFIG_KEY_TRUST_LOOSE_REF_STAT = "trustLooseRefStat"; + + /** + * The "trustLooseRefStat" key + * + * @since 7.2 + */ + public static final String CONFIG_KEY_TRUST_PACK_STAT = "trustPackStat"; + + /** + * The "trustLooseObjectFileStat" key + * + * @since 7.2 + */ + public static final String CONFIG_KEY_TRUST_LOOSE_OBJECT_STAT = "trustLooseObjectStat"; + + /** + * The "trustTablesListStat" key + * + * @since 7.2 + */ + public static final String CONFIG_KEY_TRUST_TABLESLIST_STAT = "trustTablesListStat"; + + /** * The "pack.preserveOldPacks" key * * @since 5.13.2 @@ -939,4 +1066,53 @@ public final class ConfigConstants { * @since 5.13.2 */ public static final String CONFIG_KEY_PRUNE_PRESERVED = "prunepreserved"; + + /** + * The "commitGraph" section + * + * @since 6.7 + */ + public static final String CONFIG_COMMIT_GRAPH_SECTION = "commitGraph"; + + /** + * The "writeChangedPaths" key + * + * @since 6.7 + */ + public static final String CONFIG_KEY_WRITE_CHANGED_PATHS = "writeChangedPaths"; + + /** + * The "readChangedPaths" key + * + * @since 6.7 + */ + public static final String CONFIG_KEY_READ_CHANGED_PATHS = "readChangedPaths"; + + /** + * The "useObjectSizeIndex" key + * + * @since 7.0 + */ + public static final String CONFIG_KEY_USE_OBJECT_SIZE_INDEX = "useObjectSizeIndex"; + + /** + * The "loadRevIndexInParallel" key + * + * @since 7.1 + */ + public static final String CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL = "loadRevIndexInParallel"; + + /** + * The "reftable" section + * + * @since 7.2 + */ + public static final String CONFIG_REFTABLE_SECTION = "reftable"; + + /** + * The "autorefresh" key + * + * @since 7.2 + */ + public static final String CONFIG_KEY_AUTOREFRESH = "autorefresh"; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java index a9235ebcdf..e9a8b44271 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java @@ -83,7 +83,6 @@ class ConfigLine { return a.equals(b); } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index 0b8bf8c6c5..9de8392690 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -12,10 +12,13 @@ package org.eclipse.jgit.lib; +import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.UTF_8; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; @@ -203,24 +206,6 @@ public final class Constants { */ public static final byte[] PACK_SIGNATURE = { 'P', 'A', 'C', 'K' }; - /** - * Native character encoding for commit messages, file names... - * - * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly - * instead. - */ - @Deprecated - public static final Charset CHARSET; - - /** - * Native character encoding for commit messages, file names... - * - * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly - * instead. - */ - @Deprecated - public static final String CHARACTER_ENCODING; - /** Default main branch name */ public static final String MASTER = "master"; @@ -273,6 +258,20 @@ public final class Constants { public static final String INFO_REFS = "info/refs"; /** + * Name of heads folder or file in refs. + * + * @since 7.0 + */ + public static final String HEADS = "heads"; + + /** + * Prefix for any log. + * + * @since 7.0 + */ + public static final String L_LOGS = LOGS + "/"; + + /** * Info alternates file (goes under OBJECTS) * @since 5.5 */ @@ -337,15 +336,24 @@ public final class Constants { public static final String GIT_CONFIG_NOSYSTEM_KEY = "GIT_CONFIG_NOSYSTEM"; /** - * The key of the XDG_CONFIG_HOME directory defined in the XDG base - * directory specification, see - * {@link "https://wiki.archlinux.org/index.php/XDG_Base_Directory"} + * The key of the XDG_CONFIG_HOME directory defined in the + * <a href="https://wiki.archlinux.org/index.php/XDG_Base_Directory"> + * XDG Base Directory specification</a>. * * @since 5.5.2 */ public static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME"; /** + * The key of the XDG_CACHE_HOME directory defined in the + * <a href="https://wiki.archlinux.org/index.php/XDG_Base_Directory"> + * XDG Base Directory specification</a>. + * + * @since 7.3 + */ + public static final String XDG_CACHE_HOME = "XDG_CACHE_HOME"; + + /** * The environment variable that limits how close to the root of the file * systems JGit will traverse when looking for a repository root. */ @@ -358,6 +366,14 @@ public final class Constants { public static final String GIT_DIR_KEY = "GIT_DIR"; /** + * The environment variable that tells us which directory is the common + * ".git" directory. + * + * @since 7.0 + */ + public static final String GIT_COMMON_DIR_KEY = "GIT_COMMON_DIR"; + + /** * The environment variable that tells us which directory is the working * directory. */ @@ -459,6 +475,36 @@ public final class Constants { public static final String GITDIR = "gitdir: "; /** + * Name of the file (inside gitDir) that references the worktree's .git + * file (opposite link). + * + * .git/worktrees/<worktree-name>/gitdir + * + * A text file containing the absolute path back to the .git file that + * points here. This file is used to verify if the linked repository has been + * manually removed in which case this directory is no longer needed. + * The modification time (mtime) of this file should be updated each time + * the linked repository is accessed. + * + * @since 7.0 + */ + public static final String GITDIR_FILE = "gitdir"; + + /** + * Name of the file (inside gitDir) that has reference to $GIT_COMMON_DIR. + * + * .git/worktrees/<worktree-name>/commondir + * + * If this file exists, $GIT_COMMON_DIR will be set to the path specified in + * this file unless it is explicitly set. If the specified path is relative, + * it is relative to $GIT_DIR. The repository with commondir is incomplete + * without the repository pointed by "commondir". + * + * @since 7.0 + */ + public static final String COMMONDIR_FILE = "commondir"; + + /** * Name of the folder (inside gitDir) where submodules are stored * * @since 3.6 @@ -494,6 +540,34 @@ public final class Constants { public static final String ATTR_BUILTIN_BINARY_MERGER = "binary"; //$NON-NLS-1$ /** + * Prefix of a GPG signature. + * + * @since 7.0 + */ + public static final String GPG_SIGNATURE_PREFIX = "-----BEGIN PGP SIGNATURE-----"; //$NON-NLS-1$ + + /** + * Prefix of a CMS signature (X.509, S/MIME). + * + * @since 7.0 + */ + public static final String CMS_SIGNATURE_PREFIX = "-----BEGIN SIGNED MESSAGE-----"; //$NON-NLS-1$ + + /** + * Prefix of an SSH signature. + * + * @since 7.0 + */ + public static final String SSH_SIGNATURE_PREFIX = "-----BEGIN SSH SIGNATURE-----"; //$NON-NLS-1$ + + /** + * Union built-in merge driver + * + * @since 6.10.1 + */ + public static final String ATTR_BUILTIN_UNION_MERGE_DRIVER = "union"; //$NON-NLS-1$ + + /** * Create a new digest function for objects. * * @return a new digest object. @@ -661,44 +735,32 @@ public final class Constants { * the 7-bit ASCII character space. */ public static byte[] encodeASCII(String s) { - final byte[] r = new byte[s.length()]; - for (int k = r.length - 1; k >= 0; k--) { - final char c = s.charAt(k); - if (c > 127) - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().notASCIIString, s)); - r[k] = (byte) c; + try { + CharsetEncoder encoder = US_ASCII.newEncoder() + .onUnmappableCharacter(CodingErrorAction.REPORT) + .onMalformedInput(CodingErrorAction.REPORT); + return encoder.encode(CharBuffer.wrap(s)).array(); + } catch (CharacterCodingException e) { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().notASCIIString, s), e); } - return r; } /** - * Convert a string to a byte array in the standard character encoding. + * Convert a string to a byte array in the standard character encoding UTF8. * * @param str * the string to convert. May contain any Unicode characters. * @return a byte array representing the requested string, encoded using the * default character encoding (UTF-8). - * @see #CHARACTER_ENCODING */ public static byte[] encode(String str) { - final ByteBuffer bb = UTF_8.encode(str); - final int len = bb.limit(); - if (bb.hasArray() && bb.arrayOffset() == 0) { - final byte[] arr = bb.array(); - if (arr.length == len) - return arr; - } - - final byte[] arr = new byte[len]; - bb.get(arr); - return arr; + return str.getBytes(UTF_8); } static { if (OBJECT_ID_LENGTH != newMessageDigest().getDigestLength()) throw new LinkageError(JGitText.get().incorrectOBJECT_ID_LENGTH); - CHARSET = UTF_8; - CHARACTER_ENCODING = UTF_8.name(); } /** name of the file containing the commit msg for a merge commit */ @@ -766,7 +828,7 @@ public final class Constants { * * @since 6.5 */ - public static int COMMIT_GENERATION_UNKNOWN = Integer.MAX_VALUE; + public static final int COMMIT_GENERATION_UNKNOWN = Integer.MAX_VALUE; /** * If a commit-graph file was written by a version of Git that did not @@ -775,7 +837,7 @@ public final class Constants { * * @since 6.5 */ - public static int COMMIT_GENERATION_NOT_COMPUTED = 0; + public static final int COMMIT_GENERATION_NOT_COMPUTED = 0; private Constants() { // Hide the default constructor diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java index 4de1801d04..0e27b2743c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java @@ -17,12 +17,16 @@ package org.eclipse.jgit.lib; import static java.util.zip.Deflater.DEFAULT_COMPRESSION; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config.SectionParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class keeps git repository core parameters. */ public class CoreConfig { + private static final Logger LOG = LoggerFactory.getLogger(CoreConfig.class); /** Key for {@link Config#get(SectionParser)}. */ public static final Config.SectionParser<CoreConfig> KEY = CoreConfig::new; @@ -127,7 +131,9 @@ public class CoreConfig { * Permissible values for {@code core.trustPackedRefsStat}. * * @since 6.1.1 + * @deprecated use {@link TrustStat} instead */ + @Deprecated(since = "7.2", forRemoval = true) public enum TrustPackedRefsStat { /** Do not trust file attributes of the packed-refs file. */ NEVER, @@ -135,27 +141,82 @@ public class CoreConfig { /** Trust file attributes of the packed-refs file. */ ALWAYS, - /** Open and close the packed-refs file to refresh its file attributes - * and then trust it. */ + /** + * Open and close the packed-refs file to refresh its file attributes + * and then trust it. + */ AFTER_OPEN, - /** {@code core.trustPackedRefsStat} defaults to this when it is - * not set */ + /** + * {@code core.trustPackedRefsStat} defaults to this when it is not set + */ UNSET } + /** + * Permissible values for {@code core.trustLooseRefStat}. + * + * @since 6.9 + * @deprecated use {@link TrustStat} instead + */ + @Deprecated(since = "7.2", forRemoval = true) + public enum TrustLooseRefStat { + + /** Trust file attributes of the loose ref. */ + ALWAYS, + + /** + * Open and close parent directories of the loose ref file until the + * repository root to refresh its file attributes and then trust it. + */ + AFTER_OPEN, + } + + /** + * Values for {@code core.trustXXX} options. + * + * @since 7.2 + */ + public enum TrustStat { + /** Do not trust file attributes of a File. */ + NEVER, + + /** Always trust file attributes of a File. */ + ALWAYS, + + /** Open and close the File to refresh its file attributes + * and then trust it. */ + AFTER_OPEN, + + /** + * Used for specific options to inherit value from value set for + * core.trustStat. + */ + INHERIT + } + private final int compression; private final int packIndexVersion; - private final LogRefUpdates logAllRefUpdates; - private final String excludesfile; private final String attributesfile; private final boolean commitGraph; + private final TrustStat trustStat; + + private final TrustStat trustPackedRefsStat; + + private final TrustStat trustLooseRefStat; + + private final TrustStat trustPackStat; + + private final TrustStat trustLooseObjectStat; + + private final TrustStat trustTablesListStat; + /** * Options for symlink handling * @@ -185,14 +246,17 @@ public class CoreConfig { DOTGITONLY } - private CoreConfig(Config rc) { + /** + * Create a new core configuration from the passed configuration. + * + * @param rc + * git configuration + */ + CoreConfig(Config rc) { compression = rc.getInt(ConfigConstants.CONFIG_CORE_SECTION, ConfigConstants.CONFIG_KEY_COMPRESSION, DEFAULT_COMPRESSION); packIndexVersion = rc.getInt(ConfigConstants.CONFIG_PACK_SECTION, ConfigConstants.CONFIG_KEY_INDEXVERSION, 2); - logAllRefUpdates = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, - LogRefUpdates.TRUE); excludesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_EXCLUDESFILE); attributesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, @@ -200,6 +264,68 @@ public class CoreConfig { commitGraph = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION, ConfigConstants.CONFIG_COMMIT_GRAPH, DEFAULT_COMMIT_GRAPH_ENABLE); + + trustStat = parseTrustStat(rc); + trustPackedRefsStat = parseTrustPackedRefsStat(rc); + trustLooseRefStat = parseTrustLooseRefStat(rc); + trustPackStat = parseTrustPackFileStat(rc); + trustLooseObjectStat = parseTrustLooseObjectFileStat(rc); + trustTablesListStat = parseTablesListStat(rc); + } + + private static TrustStat parseTrustStat(Config rc) { + Boolean tfs = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION, + ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT); + TrustStat ts = rc.getEnum(TrustStat.values(), + ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_TRUST_STAT); + if (tfs != null) { + if (ts == null) { + LOG.warn(JGitText.get().deprecatedTrustFolderStat); + return tfs.booleanValue() ? TrustStat.ALWAYS : TrustStat.NEVER; + } + LOG.warn(JGitText.get().precedenceTrustConfig); + } + if (ts == null) { + ts = TrustStat.ALWAYS; + } else if (ts == TrustStat.INHERIT) { + LOG.warn(JGitText.get().invalidTrustStat); + ts = TrustStat.ALWAYS; + } + return ts; + } + + private TrustStat parseTrustPackedRefsStat(Config rc) { + return inheritParseTrustStat(rc, + ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT); + } + + private TrustStat parseTrustLooseRefStat(Config rc) { + return inheritParseTrustStat(rc, + ConfigConstants.CONFIG_KEY_TRUST_LOOSE_REF_STAT); + } + + private TrustStat parseTrustPackFileStat(Config rc) { + return inheritParseTrustStat(rc, + ConfigConstants.CONFIG_KEY_TRUST_PACK_STAT); + } + + private TrustStat parseTrustLooseObjectFileStat(Config rc) { + return inheritParseTrustStat(rc, + ConfigConstants.CONFIG_KEY_TRUST_LOOSE_OBJECT_STAT); + } + + private TrustStat inheritParseTrustStat(Config rc, String key) { + TrustStat t = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, key, + TrustStat.INHERIT); + return t == TrustStat.INHERIT ? trustStat : t; + } + + private TrustStat parseTablesListStat(Config rc) { + TrustStat t = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_TRUST_TABLESLIST_STAT, + TrustStat.INHERIT); + return t == TrustStat.INHERIT ? trustStat : t; } /** @@ -221,20 +347,6 @@ public class CoreConfig { } /** - * Whether to log all refUpdates - * - * @return whether to log all refUpdates - * @deprecated since 5.6; default value depends on whether the repository is - * bare. Use - * {@link Config#getEnum(String, String, String, Enum)} - * directly. - */ - @Deprecated - public boolean isLogAllRefUpdates() { - return !LogRefUpdates.FALSE.equals(logAllRefUpdates); - } - - /** * Get path of excludesfile * * @return path of excludesfile @@ -264,4 +376,70 @@ public class CoreConfig { public boolean enableCommitGraph() { return commitGraph; } + + /** + * Get how far we can trust file attributes of packed-refs file which is + * used to store {@link org.eclipse.jgit.lib.Ref}s in + * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}. + * + * @return how far we can trust file attributes of packed-refs file. + * + * @since 7.2 + */ + public TrustStat getTrustPackedRefsStat() { + return trustPackedRefsStat; + } + + /** + * Get how far we can trust file attributes of loose ref files which are + * used to store {@link org.eclipse.jgit.lib.Ref}s in + * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}. + * + * @return how far we can trust file attributes of loose ref files. + * + * @since 7.2 + */ + public TrustStat getTrustLooseRefStat() { + return trustLooseRefStat; + } + + /** + * Get how far we can trust file attributes of packed-refs file which is + * used to store {@link org.eclipse.jgit.lib.Ref}s in + * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}. + * + * @return how far we can trust file attributes of packed-refs file. + * + * @since 7.2 + */ + public TrustStat getTrustPackStat() { + return trustPackStat; + } + + /** + * Get how far we can trust file attributes of loose ref files which are + * used to store {@link org.eclipse.jgit.lib.Ref}s in + * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}. + * + * @return how far we can trust file attributes of loose ref files. + * + * @since 7.2 + */ + public TrustStat getTrustLooseObjectStat() { + return trustLooseObjectStat; + } + + /** + * Get how far we can trust file attributes of the "tables.list" file which + * is used to store the list of filenames of the files storing + * {@link org.eclipse.jgit.internal.storage.reftable.Reftable}s in + * {@link org.eclipse.jgit.internal.storage.file.FileReftableDatabase}. + * + * @return how far we can trust file attributes of the "tables.list" file. + * + * @since 7.2 + */ + public TrustStat getTrustTablesListStat() { + return trustTablesListStat; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java index 86409403b0..3059f283fe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java @@ -18,6 +18,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config.ConfigEnum; import org.eclipse.jgit.transport.RefSpec; @@ -31,29 +32,37 @@ import org.eclipse.jgit.util.StringUtils; */ public class DefaultTypedConfigGetter implements TypedConfigGetter { - /** {@inheritDoc} */ + @SuppressWarnings("boxed") @Override public boolean getBoolean(Config config, String section, String subsection, String name, boolean defaultValue) { - String n = config.getRawString(section, subsection, name); + return neverNull(getBoolean(config, section, subsection, name, + Boolean.valueOf(defaultValue))); + } + + @Nullable + @Override + public Boolean getBoolean(Config config, String section, String subsection, + String name, @Nullable Boolean defaultValue) { + String n = config.getString(section, subsection, name); if (n == null) { return defaultValue; } if (Config.isMissing(n)) { - return true; + return Boolean.TRUE; } try { - return StringUtils.toBoolean(n); + return Boolean.valueOf(StringUtils.toBoolean(n)); } catch (IllegalArgumentException err) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidBooleanValue, section, name, n), err); } } - /** {@inheritDoc} */ + @Nullable @Override public <T extends Enum<?>> T getEnum(Config config, T[] all, String section, - String subsection, String name, T defaultValue) { + String subsection, String name, @Nullable T defaultValue) { String value = config.getString(section, subsection, name); if (value == null) { return defaultValue; @@ -106,62 +115,102 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter { JGitText.get().enumValueNotSupported2, section, name, value)); } - /** {@inheritDoc} */ @Override public int getInt(Config config, String section, String subsection, String name, int defaultValue) { - long val = config.getLong(section, subsection, name, defaultValue); + return neverNull(getInt(config, section, subsection, name, + Integer.valueOf(defaultValue))); + } + + @Nullable + @Override + @SuppressWarnings("boxing") + public Integer getInt(Config config, String section, String subsection, + String name, @Nullable Integer defaultValue) { + Long longDefault = defaultValue != null + ? Long.valueOf(defaultValue.longValue()) + : null; + Long val = config.getLong(section, subsection, name); + if (val == null) { + val = longDefault; + } + if (val == null) { + return null; + } if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) { - return (int) val; + return Integer.valueOf(Math.toIntExact(val)); } throw new IllegalArgumentException(MessageFormat .format(JGitText.get().integerValueOutOfRange, section, name)); } - /** {@inheritDoc} */ @Override public int getIntInRange(Config config, String section, String subsection, String name, int minValue, int maxValue, int defaultValue) { - int val = getInt(config, section, subsection, name, defaultValue); + return neverNull(getIntInRange(config, section, subsection, name, + minValue, maxValue, Integer.valueOf(defaultValue))); + } + + @Override + @SuppressWarnings("boxing") + public Integer getIntInRange(Config config, String section, + String subsection, String name, int minValue, int maxValue, + Integer defaultValue) { + Integer val = getInt(config, section, subsection, name, defaultValue); + if (val == null) { + return null; + } if ((val >= minValue && val <= maxValue) || val == UNSET_INT) { return val; } if (subsection == null) { - throw new IllegalArgumentException(MessageFormat.format( - JGitText.get().integerValueNotInRange, section, name, - Integer.valueOf(val), Integer.valueOf(minValue), - Integer.valueOf(maxValue))); + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().integerValueNotInRange, + section, name, val, minValue, maxValue)); } throw new IllegalArgumentException(MessageFormat.format( JGitText.get().integerValueNotInRangeSubSection, section, - subsection, name, Integer.valueOf(val), - Integer.valueOf(minValue), Integer.valueOf(maxValue))); + subsection, name, val, minValue, maxValue)); } - /** {@inheritDoc} */ @Override public long getLong(Config config, String section, String subsection, String name, long defaultValue) { - final String str = config.getString(section, subsection, name); + return neverNull(getLong(config, section, subsection, name, + Long.valueOf(defaultValue))); + } + + @Nullable + @Override + public Long getLong(Config config, String section, String subsection, + String name, @Nullable Long defaultValue) { + String str = config.getString(section, subsection, name); if (str == null) { return defaultValue; } try { - return StringUtils.parseLongWithSuffix(str, false); + return Long.valueOf(StringUtils.parseLongWithSuffix(str, false)); } catch (StringIndexOutOfBoundsException e) { // Empty return defaultValue; } catch (NumberFormatException nfe) { - throw new IllegalArgumentException(MessageFormat.format( - JGitText.get().invalidIntegerValue, section, name, str), + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().invalidIntegerValue, + section, name, str), nfe); } } - /** {@inheritDoc} */ @Override public long getTimeUnit(Config config, String section, String subsection, String name, long defaultValue, TimeUnit wantUnit) { + return neverNull(getTimeUnit(config, section, subsection, name, + Long.valueOf(defaultValue), wantUnit)); + } + + @Override + public Long getTimeUnit(Config config, String section, String subsection, + String name, @Nullable Long defaultValue, TimeUnit wantUnit) { String valueString = config.getString(section, subsection, name); if (valueString == null) { @@ -238,8 +287,8 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter { } try { - return wantUnit.convert(Long.parseLong(digits) * inputMul, - inputUnit); + return Long.valueOf(wantUnit + .convert(Long.parseLong(digits) * inputMul, inputUnit)); } catch (NumberFormatException nfe) { IllegalArgumentException iae = notTimeUnit(section, subsection, unitName, valueString); @@ -269,7 +318,6 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter { section, name, valueString)); } - /** {@inheritDoc} */ @Override @NonNull public List<RefSpec> getRefSpecs(Config config, String section, @@ -281,4 +329,14 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter { } return result; } + + // Trick for the checkers. When we use this, one is never null, but + // they don't know. + @NonNull + private static <T> T neverNull(T one) { + if (one == null) { + throw new IllegalArgumentException(); + } + return one; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java index 94d28eb345..a5410b778c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java @@ -18,31 +18,26 @@ package org.eclipse.jgit.lib; */ public abstract class EmptyProgressMonitor implements ProgressMonitor { - /** {@inheritDoc} */ @Override public void start(int totalTasks) { // empty } - /** {@inheritDoc} */ @Override public void beginTask(String title, int totalWork) { // empty } - /** {@inheritDoc} */ @Override public void update(int completed) { // empty } - /** {@inheritDoc} */ @Override public void endTask() { // empty } - /** {@inheritDoc} */ @Override public boolean isCancelled() { return false; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java index bb72e12510..c75d81c8b5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java @@ -176,12 +176,12 @@ public abstract class FileMode { * Test a file mode for equality with this * {@link org.eclipse.jgit.lib.FileMode} object. * - * @param modebits + * @param modeBits * a int. * @return true if the mode bits represent the same mode as this object */ @SuppressWarnings("NonOverridingEquals") - public abstract boolean equals(int modebits); + public abstract boolean equals(int modeBits); /** * Copy this mode as a sequence of octal US-ASCII bytes. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java new file mode 100644 index 0000000000..073bf7a0ca --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2023, Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lib; + +import java.io.File; +import java.io.IOException; +import java.nio.file.InvalidPathException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; + +/** + * A hierarchical cache of {@link FileMode}s per git path. + * + * @since 6.6.1 + */ +public class FileModeCache { + + @NonNull + private final CacheItem root = new CacheItem(FileMode.TREE); + + @NonNull + private final Repository repo; + + /** + * Creates a new {@link FileModeCache} for a {@link Repository}. + * + * @param repo + * {@link Repository} this cache is for + */ + public FileModeCache(@NonNull Repository repo) { + this.repo = repo; + } + + /** + * Retrieves the {@link Repository}. + * + * @return the {@link Repository} this {@link FileModeCache} was created for + */ + @NonNull + public Repository getRepository() { + return repo; + } + + /** + * Obtains the {@link CacheItem} for the working tree root. + * + * @return the {@link CacheItem} + */ + @NonNull + public CacheItem getRoot() { + return root; + } + + /** + * Ensure that the given parent directory exists, and cache the information + * that gitPath refers to a file. + * + * @param gitPath + * of the file to be written + * @param parentDir + * directory in which the file shall be placed, assumed to be the + * parent of the {@code gitPath} + * @param makeSpace + * whether to delete a possibly existing file at + * {@code parentDir} + * @throws IOException + * if the directory cannot be created, if necessary + */ + public void safeCreateParentDirectory(String gitPath, File parentDir, + boolean makeSpace) throws IOException { + CacheItem cachedParent = safeCreateDirectory(gitPath, parentDir, + makeSpace); + cachedParent.remove(gitPath.substring(gitPath.lastIndexOf('/') + 1)); + } + + /** + * Ensures the given directory {@code dir} with the given git path exists. + * + * @param gitPath + * of a file to be written + * @param dir + * directory in which the file shall be placed, assumed to be the + * parent of the {@code gitPath} + * @param makeSpace + * whether to remove a file that already at that name + * @return A {@link CacheItem} describing the directory, which is guaranteed + * to exist + * @throws IOException + * if the directory cannot be made to exist at the given + * location + */ + public CacheItem safeCreateDirectory(String gitPath, File dir, + boolean makeSpace) throws IOException { + FS fs = repo.getFS(); + int i = gitPath.lastIndexOf('/'); + String parentPath = null; + if (i >= 0) { + if ((makeSpace && dir.isFile()) || fs.isSymLink(dir)) { + FileUtils.delete(dir); + } + parentPath = gitPath.substring(0, i); + deleteSymlinkParent(fs, parentPath, repo.getWorkTree()); + } + FileUtils.mkdirs(dir, true); + CacheItem cachedParent = getRoot(); + if (parentPath != null) { + cachedParent = add(parentPath, FileMode.TREE); + } + return cachedParent; + } + + private void deleteSymlinkParent(FS fs, String gitPath, File workingTree) + throws IOException { + if (!fs.supportsSymlinks()) { + return; + } + String[] parts = gitPath.split("/"); //$NON-NLS-1$ + int n = parts.length; + CacheItem cached = getRoot(); + File p = workingTree; + for (int i = 0; i < n; i++) { + p = new File(p, parts[i]); + CacheItem cachedChild = cached != null ? cached.child(parts[i]) + : null; + boolean delete = false; + if (cachedChild != null) { + if (FileMode.SYMLINK.equals(cachedChild.getMode())) { + delete = true; + } + } else { + try { + Path nioPath = FileUtils.toPath(p); + BasicFileAttributes attributes = nioPath.getFileSystem() + .provider() + .getFileAttributeView(nioPath, + BasicFileAttributeView.class, + LinkOption.NOFOLLOW_LINKS) + .readAttributes(); + if (attributes.isSymbolicLink()) { + delete = p.isDirectory(); + } else if (attributes.isRegularFile()) { + break; + } + } catch (InvalidPathException | IOException e) { + // If we can't get the attributes the path does not exist, + // or if it does a subsequent mkdirs() will also throw an + // exception. + break; + } + } + if (delete) { + // Deletes the symlink + FileUtils.delete(p, FileUtils.SKIP_MISSING); + if (cached != null) { + cached.remove(parts[i]); + } + break; + } + cached = cachedChild; + } + } + + /** + * Records the given {@link FileMode} for the given git path in the cache. + * If an entry already exists for the given path, the previously cached file + * mode is overwritten. + * + * @param gitPath + * to cache the {@link FileMode} for + * @param finalMode + * {@link FileMode} to cache + * @return the {@link CacheItem} for the path + */ + @NonNull + private CacheItem add(String gitPath, FileMode finalMode) { + if (gitPath.isEmpty()) { + throw new IllegalArgumentException(); + } + String[] parts = gitPath.split("/"); //$NON-NLS-1$ + int n = parts.length; + int i = 0; + CacheItem curr = getRoot(); + while (i < n) { + CacheItem next = curr.child(parts[i]); + if (next == null) { + break; + } + curr = next; + i++; + } + if (i == n) { + curr.setMode(finalMode); + } else { + while (i < n) { + curr = curr.insert(parts[i], + i + 1 == n ? finalMode : FileMode.TREE); + i++; + } + } + return curr; + } + + /** + * An item from a {@link FileModeCache}, recording information about a git + * path (known from context). + */ + public static class CacheItem { + + @NonNull + private FileMode mode; + + private Map<String, CacheItem> children; + + /** + * Creates a new {@link CacheItem}. + * + * @param mode + * {@link FileMode} to cache + */ + public CacheItem(@NonNull FileMode mode) { + this.mode = mode; + } + + /** + * Retrieves the cached {@link FileMode}. + * + * @return the {@link FileMode} + */ + @NonNull + public FileMode getMode() { + return mode; + } + + /** + * Retrieves an immediate child of this {@link CacheItem} by name. + * + * @param childName + * name of the child to get + * @return the {@link CacheItem}, or {@code null} if no such child is + * known + */ + public CacheItem child(String childName) { + if (children == null) { + return null; + } + return children.get(childName); + } + + /** + * Inserts a new cached {@link FileMode} as an immediate child of this + * {@link CacheItem}. If there is already a child with the same name, it + * is overwritten. + * + * @param childName + * name of the child to create + * @param childMode + * {@link FileMode} to cache + * @return the new {@link CacheItem} created for the child + */ + public CacheItem insert(String childName, @NonNull FileMode childMode) { + if (!FileMode.TREE.equals(mode)) { + throw new IllegalArgumentException(); + } + if (children == null) { + children = new HashMap<>(); + } + CacheItem newItem = new CacheItem(childMode); + children.put(childName, newItem); + return newItem; + } + + /** + * Removes the immediate child with the given name. + * + * @param childName + * name of the child to remove + * @return the previously cached {@link CacheItem}, if any + */ + public CacheItem remove(String childName) { + if (children == null) { + return null; + } + return children.remove(childName); + } + + void setMode(@NonNull FileMode mode) { + this.mode = mode; + if (!FileMode.TREE.equals(mode)) { + children = null; + } + } + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java index aa0a9dd663..d03db772ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java @@ -36,6 +36,8 @@ public final class GitmoduleEntry { } /** + * Get Id of a .gitmodules file found in the pack + * * @return Id of a .gitmodules file found in the pack */ public AnyObjectId getBlobId() { @@ -43,6 +45,8 @@ public final class GitmoduleEntry { } /** + * Get Id of a tree object where the .gitmodules file was found + * * @return Id of a tree object where the .gitmodules file was found */ public AnyObjectId getTreeId() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java index 427a235f3b..23d16db39f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java @@ -24,7 +24,13 @@ public class GpgConfig { /** Value for openpgp */ OPENPGP("openpgp"), //$NON-NLS-1$ /** Value for x509 */ - X509("x509"); //$NON-NLS-1$ + X509("x509"), //$NON-NLS-1$ + /** + * Value for ssh. + * + * @since 7.0 + */ + SSH("ssh"); //$NON-NLS-1$ private final String configValue; @@ -55,26 +61,11 @@ public class GpgConfig { private final boolean forceAnnotated; - /** - * Create a {@link GpgConfig} with the given parameters and default - * {@code true} for signing commits and {@code false} for tags. - * - * @param keySpec - * to use - * @param format - * to use - * @param gpgProgram - * to use - * @since 5.11 - */ - public GpgConfig(String keySpec, GpgFormat format, String gpgProgram) { - keyFormat = format; - signingKey = keySpec; - program = gpgProgram; - signCommits = true; - signAllTags = false; - forceAnnotated = false; - } + private final String sshDefaultKeyCommand; + + private final String sshAllowedSignersFile; + + private final String sshRevocationFile; /** * Create a new GPG config that reads the configuration from config. @@ -83,18 +74,18 @@ public class GpgConfig { * the config to read from */ public GpgConfig(Config config) { - keyFormat = config.getEnum(GpgFormat.values(), - ConfigConstants.CONFIG_GPG_SECTION, null, + keyFormat = config.getEnum(ConfigConstants.CONFIG_GPG_SECTION, null, ConfigConstants.CONFIG_KEY_FORMAT, GpgFormat.OPENPGP); signingKey = config.getString(ConfigConstants.CONFIG_USER_SECTION, null, ConfigConstants.CONFIG_KEY_SIGNINGKEY); String exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION, keyFormat.toConfigValue(), ConfigConstants.CONFIG_KEY_PROGRAM); - if (exe == null) { + if (exe == null && GpgFormat.OPENPGP.equals(keyFormat)) { exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION, null, ConfigConstants.CONFIG_KEY_PROGRAM); } + program = exe; signCommits = config.getBoolean(ConfigConstants.CONFIG_COMMIT_SECTION, ConfigConstants.CONFIG_KEY_GPGSIGN, false); @@ -102,6 +93,17 @@ public class GpgConfig { ConfigConstants.CONFIG_KEY_GPGSIGN, false); forceAnnotated = config.getBoolean(ConfigConstants.CONFIG_TAG_SECTION, ConfigConstants.CONFIG_KEY_FORCE_SIGN_ANNOTATED, false); + sshDefaultKeyCommand = config.getString( + ConfigConstants.CONFIG_GPG_SECTION, + ConfigConstants.CONFIG_SSH_SUBSECTION, + ConfigConstants.CONFIG_KEY_SSH_DEFAULT_KEY_COMMAND); + sshAllowedSignersFile = config.getString( + ConfigConstants.CONFIG_GPG_SECTION, + ConfigConstants.CONFIG_SSH_SUBSECTION, + ConfigConstants.CONFIG_KEY_SSH_ALLOWED_SIGNERS_FILE); + sshRevocationFile = config.getString(ConfigConstants.CONFIG_GPG_SECTION, + ConfigConstants.CONFIG_SSH_SUBSECTION, + ConfigConstants.CONFIG_KEY_SSH_REVOCATION_FILE); } /** @@ -165,4 +167,37 @@ public class GpgConfig { public boolean isSignAnnotated() { return forceAnnotated; } + + /** + * Retrieves the value of git config {@code gpg.ssh.defaultKeyCommand}. + * + * @return the value of {@code gpg.ssh.defaultKeyCommand} + * + * @since 7.1 + */ + public String getSshDefaultKeyCommand() { + return sshDefaultKeyCommand; + } + + /** + * Retrieves the value of git config {@code gpg.ssh.allowedSignersFile}. + * + * @return the value of {@code gpg.ssh.allowedSignersFile} + * + * @since 7.1 + */ + public String getSshAllowedSignersFile() { + return sshAllowedSignersFile; + } + + /** + * Retrieves the value of git config {@code gpg.ssh.revocationFile}. + * + * @return the value of {@code gpg.ssh.revocationFile} + * + * @since 7.1 + */ + public String getSshRevocationFile() { + return sshRevocationFile; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java deleted file mode 100644 index 074f46567b..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib; - -import org.eclipse.jgit.annotations.NonNull; -import org.eclipse.jgit.annotations.Nullable; -import org.eclipse.jgit.api.errors.CanceledException; -import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; -import org.eclipse.jgit.transport.CredentialsProvider; - -/** - * Creates GPG signatures for Git objects. - * - * @since 5.11 - */ -public interface GpgObjectSigner { - - /** - * Signs the specified object. - * - * <p> - * Implementors should obtain the payload for signing from the specified - * object via {@link ObjectBuilder#build()} and create a proper - * {@link GpgSignature}. The generated signature must be set on the - * specified {@code object} (see - * {@link ObjectBuilder#setGpgSignature(GpgSignature)}). - * </p> - * <p> - * Any existing signature on the object must be discarded prior obtaining - * the payload via {@link ObjectBuilder#build()}. - * </p> - * - * @param object - * the object to sign (must not be {@code null} and must be - * complete to allow proper calculation of payload) - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @param config - * GPG settings from the git config - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - * @throws UnsupportedSigningFormatException - * if a config is given and the wanted key format is not - * supported - */ - void signObject(@NonNull ObjectBuilder object, - @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider, GpgConfig config) - throws CanceledException, UnsupportedSigningFormatException; - - /** - * Indicates if a signing key is available for the specified committer - * and/or signing key. - * - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @param config - * GPG settings from the git config - * @return <code>true</code> if a signing key is available, - * <code>false</code> otherwise - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - * @throws UnsupportedSigningFormatException - * if a config is given and the wanted key format is not - * supported - */ - public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey, - @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider, GpgConfig config) - throws CanceledException, UnsupportedSigningFormatException; - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java index e78cf16ea4..4018dabebe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java @@ -49,7 +49,6 @@ public class GpgSignature implements Serializable { return new String(signature, US_ASCII); } - /** {@inheritDoc} */ @Override @SuppressWarnings("nls") public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java deleted file mode 100644 index a7a39c998f..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib; - -import java.io.IOException; -import java.util.Date; - -import org.eclipse.jgit.annotations.NonNull; -import org.eclipse.jgit.annotations.Nullable; -import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.revwalk.RevObject; - -/** - * A {@code GpgVerifier} can verify GPG signatures on git commits and tags. - * - * @since 5.11 - */ -public interface GpgSignatureVerifier { - - /** - * Verifies the signature on a signed commit or tag. - * - * @param object - * to verify - * @param config - * the {@link GpgConfig} to use - * @return a {@link SignatureVerification} describing the outcome of the - * verification, or {@code null} if the object was not signed - * @throws IOException - * if an error occurs getting a public key - * @throws org.eclipse.jgit.api.errors.JGitInternalException - * if signature verification fails - */ - @Nullable - SignatureVerification verifySignature(@NonNull RevObject object, - @NonNull GpgConfig config) throws IOException; - - - /** - * Verifies a given signature for given data. - * - * @param data - * the signature is for - * @param signatureData - * the ASCII-armored signature - * @return a {@link SignatureVerification} describing the outcome - * @throws IOException - * if the signature cannot be parsed - * @throws JGitInternalException - * if signature verification fails - */ - public SignatureVerification verify(byte[] data, byte[] signatureData) - throws IOException; - - /** - * Retrieves the name of this verifier. This should be a short string - * identifying the engine that verified the signature, like "gpg" if GPG is - * used, or "bc" for a BouncyCastle implementation. - * - * @return the name - */ - @NonNull - String getName(); - - /** - * A {@link GpgSignatureVerifier} may cache public keys to speed up - * verifying signatures on multiple objects. This clears this cache, if any. - */ - void clear(); - - /** - * A {@code SignatureVerification} returns data about a (positively or - * negatively) verified signature. - */ - interface SignatureVerification { - - // Data about the signature. - - @NonNull - Date getCreationDate(); - - // Data from the signature used to find a public key. - - /** - * Obtains the signer as stored in the signature, if known. - * - * @return the signer, or {@code null} if unknown - */ - String getSigner(); - - /** - * Obtains the short or long fingerprint of the public key as stored in - * the signature, if known. - * - * @return the fingerprint, or {@code null} if unknown - */ - String getKeyFingerprint(); - - // Some information about the found public key. - - /** - * Obtains the OpenPGP user ID associated with the key. - * - * @return the user id, or {@code null} if unknown - */ - String getKeyUser(); - - /** - * Tells whether the public key used for this signature verification was - * expired when the signature was created. - * - * @return {@code true} if the key was expired already, {@code false} - * otherwise - */ - boolean isExpired(); - - /** - * Obtains the trust level of the public key used to verify the - * signature. - * - * @return the trust level - */ - @NonNull - TrustLevel getTrustLevel(); - - // The verification result. - - /** - * Tells whether the signature verification was successful. - * - * @return {@code true} if the signature was verified successfully; - * {@code false} if not. - */ - boolean getVerified(); - - /** - * Obtains a human-readable message giving additional information about - * the outcome of the verification. - * - * @return the message, or {@code null} if none set. - */ - String getMessage(); - } - - /** - * The owner's trust in a public key. - */ - enum TrustLevel { - UNKNOWN, NEVER, MARGINAL, FULL, ULTIMATE - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java deleted file mode 100644 index 59775c475b..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib; - -import java.util.Iterator; -import java.util.ServiceConfigurationError; -import java.util.ServiceLoader; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@code GpgSignatureVerifierFactory} creates {@link GpgSignatureVerifier} instances. - * - * @since 5.11 - */ -public abstract class GpgSignatureVerifierFactory { - - private static final Logger LOG = LoggerFactory - .getLogger(GpgSignatureVerifierFactory.class); - - private static class DefaultFactory { - - private static volatile GpgSignatureVerifierFactory defaultFactory = loadDefault(); - - private static GpgSignatureVerifierFactory loadDefault() { - try { - ServiceLoader<GpgSignatureVerifierFactory> loader = ServiceLoader - .load(GpgSignatureVerifierFactory.class); - Iterator<GpgSignatureVerifierFactory> iter = loader.iterator(); - if (iter.hasNext()) { - return iter.next(); - } - } catch (ServiceConfigurationError e) { - LOG.error(e.getMessage(), e); - } - return null; - } - - private DefaultFactory() { - // No instantiation - } - - public static GpgSignatureVerifierFactory getDefault() { - return defaultFactory; - } - - /** - * Sets the default factory. - * - * @param factory - * the new default factory - */ - public static void setDefault(GpgSignatureVerifierFactory factory) { - defaultFactory = factory; - } - } - - /** - * Retrieves the default factory. - * - * @return the default factory or {@code null} if none set - */ - public static GpgSignatureVerifierFactory getDefault() { - return DefaultFactory.getDefault(); - } - - /** - * Sets the default factory. - * - * @param factory - * the new default factory - */ - public static void setDefault(GpgSignatureVerifierFactory factory) { - DefaultFactory.setDefault(factory); - } - - /** - * Creates a new {@link GpgSignatureVerifier}. - * - * @return the new {@link GpgSignatureVerifier} - */ - public abstract GpgSignatureVerifier getVerifier(); - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java deleted file mode 100644 index b25a61b506..0000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2018, 2022 Salesforce and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Distribution License v. 1.0 which is available at - * https://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: BSD-3-Clause - */ -package org.eclipse.jgit.lib; - -import java.util.Iterator; -import java.util.ServiceConfigurationError; -import java.util.ServiceLoader; - -import org.eclipse.jgit.annotations.NonNull; -import org.eclipse.jgit.annotations.Nullable; -import org.eclipse.jgit.api.errors.CanceledException; -import org.eclipse.jgit.transport.CredentialsProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Creates GPG signatures for Git objects. - * - * @since 5.3 - */ -public abstract class GpgSigner { - - private static final Logger LOG = LoggerFactory.getLogger(GpgSigner.class); - - private static class DefaultSigner { - - private static volatile GpgSigner defaultSigner = loadGpgSigner(); - - private static GpgSigner loadGpgSigner() { - try { - ServiceLoader<GpgSigner> loader = ServiceLoader - .load(GpgSigner.class); - Iterator<GpgSigner> iter = loader.iterator(); - if (iter.hasNext()) { - return iter.next(); - } - } catch (ServiceConfigurationError e) { - LOG.error(e.getMessage(), e); - } - return null; - } - - private DefaultSigner() { - // No instantiation - } - - public static GpgSigner getDefault() { - return defaultSigner; - } - - public static void setDefault(GpgSigner signer) { - defaultSigner = signer; - } - } - - /** - * Get the default signer, or <code>null</code>. - * - * @return the default signer, or <code>null</code>. - */ - public static GpgSigner getDefault() { - return DefaultSigner.getDefault(); - } - - /** - * Set the default signer. - * - * @param signer - * the new default signer, may be <code>null</code> to select no - * default. - */ - public static void setDefault(GpgSigner signer) { - DefaultSigner.setDefault(signer); - } - - /** - * Signs the specified commit. - * - * <p> - * Implementors should obtain the payload for signing from the specified - * commit via {@link CommitBuilder#build()} and create a proper - * {@link GpgSignature}. The generated signature must be set on the - * specified {@code commit} (see - * {@link CommitBuilder#setGpgSignature(GpgSignature)}). - * </p> - * <p> - * Any existing signature on the commit must be discarded prior obtaining - * the payload via {@link CommitBuilder#build()}. - * </p> - * - * @param commit - * the commit to sign (must not be <code>null</code> and must be - * complete to allow proper calculation of payload) - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - */ - public abstract void sign(@NonNull CommitBuilder commit, - @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider) throws CanceledException; - - /** - * Indicates if a signing key is available for the specified committer - * and/or signing key. - * - * @param gpgSigningKey - * the signing key to locate (passed as is to the GPG signing - * tool as is; eg., value of <code>user.signingkey</code>) - * @param committer - * the signing identity (to help with key lookup in case signing - * key is not specified) - * @param credentialsProvider - * provider to use when querying for signing key credentials (eg. - * passphrase) - * @return <code>true</code> if a signing key is available, - * <code>false</code> otherwise - * @throws CanceledException - * when signing was canceled (eg., user aborted when entering - * passphrase) - */ - public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey, - @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider) throws CanceledException; - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java index df9fd47efa..a99c64701f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java @@ -122,6 +122,8 @@ public class IndexDiff { } /** + * Whether there is a "base" stage entry + * * @return whether there is a "base" stage entry */ public boolean hasBase() { @@ -129,6 +131,8 @@ public class IndexDiff { } /** + * Whether there is an "ours" stage entry + * * @return whether there is an "ours" stage entry */ public boolean hasOurs() { @@ -136,6 +140,8 @@ public class IndexDiff { } /** + * Whether there is a "theirs" stage entry + * * @return whether there is a "theirs" stage entry */ public boolean hasTheirs() { @@ -265,6 +271,7 @@ public class IndexDiff { * @param workingTreeIterator * iterator for working directory * @throws java.io.IOException + * if an IO error occurred */ public IndexDiff(Repository repository, String revstr, WorkingTreeIterator workingTreeIterator) throws IOException { @@ -281,6 +288,7 @@ public class IndexDiff { * @param workingTreeIterator * iterator for working directory * @throws java.io.IOException + * if an IO error occurred */ public IndexDiff(Repository repository, ObjectId objectId, WorkingTreeIterator workingTreeIterator) throws IOException { @@ -312,6 +320,8 @@ public class IndexDiff { */ public interface WorkingTreeIteratorFactory { /** + * Get working tree iterator + * * @param repo * the repository * @return working tree iterator @@ -325,6 +335,7 @@ public class IndexDiff { * Allows higher layers to set the factory for WorkingTreeIterators. * * @param wTreeIt + * working tree iterator factory * @since 3.6 */ public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) { @@ -349,6 +360,7 @@ public class IndexDiff { * * @return if anything is different between index, tree, and workdir * @throws java.io.IOException + * if an IO error occurred */ public boolean diff() throws IOException { return diff(null); @@ -372,6 +384,7 @@ public class IndexDiff { * {@link RepositoryBuilder}. * @return if anything is different between index, tree, and workdir * @throws java.io.IOException + * if an IO error occurred * @since 5.6 */ public boolean diff(RepositoryBuilderFactory factory) @@ -395,9 +408,11 @@ public class IndexDiff { * number or estimated files in the working tree * @param estIndexSize * number of estimated entries in the cache - * @param title a {@link java.lang.String} object. + * @param title + * a {@link java.lang.String} object. * @return if anything is different between index, tree, and workdir * @throws java.io.IOException + * if an IO error occurred */ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize, int estIndexSize, final String title) @@ -436,6 +451,7 @@ public class IndexDiff { * {@link RepositoryBuilder}. * @return if anything is different between index, tree, and workdir * @throws java.io.IOException + * if an IO error occurred * @since 5.6 */ public boolean diff(ProgressMonitor monitor, int estWorkTreeSize, @@ -623,7 +639,7 @@ public class IndexDiff { // submodule repository in .git/modules doesn't // exist yet it isn't "missing". File gitDir = new File( - new File(repository.getDirectory(), + new File(repository.getCommonDirectory(), Constants.MODULES), subRepoPath); if (!gitDir.isDirectory()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java index 4a712ba360..6a35dedcf5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java @@ -242,7 +242,6 @@ public class MutableObjectId extends AnyObjectId { } } - /** {@inheritDoc} */ @Override public ObjectId toObjectId() { return new ObjectId(this); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java index 127cca9d1b..0a4db2a5f6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java @@ -23,31 +23,26 @@ public class NullProgressMonitor implements ProgressMonitor { // Do not let others instantiate } - /** {@inheritDoc} */ @Override public void start(int totalTasks) { // Do not report. } - /** {@inheritDoc} */ @Override public void beginTask(String title, int totalWork) { // Do not report. } - /** {@inheritDoc} */ @Override public void update(int completed) { // Do not report. } - /** {@inheritDoc} */ @Override public boolean isCancelled() { return false; } - /** {@inheritDoc} */ @Override public void endTask() { // Do not report. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java index 4b7054f72b..064d89e084 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java @@ -72,7 +72,6 @@ public abstract class ObjectBuilder { * <li>create {@link GpgSignature} from payload</li> * <li>set {@link GpgSignature}</li> * </ol> - * </p> * * @param gpgSignature * the signature to set or {@code null} to unset diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index 9e9ef882a3..1a6c8ad0d2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -116,46 +116,103 @@ public class ObjectChecker { * @since 4.2 */ public enum ErrorType { - // @formatter:off // These names match git-core so that fsck section keys also match. - /***/ NULL_SHA1, - /***/ DUPLICATE_ENTRIES, - /***/ TREE_NOT_SORTED, - /***/ ZERO_PADDED_FILEMODE, - /***/ EMPTY_NAME, - /***/ FULL_PATHNAME, - /***/ HAS_DOT, - /***/ HAS_DOTDOT, - /***/ HAS_DOTGIT, - /***/ BAD_OBJECT_SHA1, - /***/ BAD_PARENT_SHA1, - /***/ BAD_TREE_SHA1, - /***/ MISSING_AUTHOR, - /***/ MISSING_COMMITTER, - /***/ MISSING_OBJECT, - /***/ MISSING_TREE, - /***/ MISSING_TYPE_ENTRY, - /***/ MISSING_TAG_ENTRY, - /***/ BAD_DATE, - /***/ BAD_EMAIL, - /***/ BAD_TIMEZONE, - /***/ MISSING_EMAIL, - /***/ MISSING_SPACE_BEFORE_DATE, - /** @since 5.2 */ GITMODULES_BLOB, - /** @since 5.2 */ GITMODULES_LARGE, - /** @since 5.2 */ GITMODULES_NAME, - /** @since 5.2 */ GITMODULES_PARSE, - /** @since 5.2 */ GITMODULES_PATH, - /** @since 5.2 */ GITMODULES_SYMLINK, - /** @since 5.2 */ GITMODULES_URL, - /***/ UNKNOWN_TYPE, + // See https://git-scm.com/docs/git-fsck#_fsck_messages + /** (WARN) Tree contains entries pointing to a null sha1. */ + NULL_SHA1, + /** (ERROR) A tree contains duplicate file entries. */ + DUPLICATE_ENTRIES, + /** (ERROR) A tree is not properly sorted. */ + TREE_NOT_SORTED, + /** (WARN) Found a zero padded filemode in a tree. */ + ZERO_PADDED_FILEMODE, + /** (WARN) A path contains an empty name. */ + EMPTY_NAME, + /** (WARN) A path contains the full path starting with "/". */ + FULL_PATHNAME, + /** (WARN) A tree contains an entry named . */ + HAS_DOT, + /** (WARN) A tree contains an entry named .. */ + HAS_DOTDOT, + /** (WARN) A tree contains an entry named .git */ + HAS_DOTGIT, + /** (ERROR) An object has a bad sha1. */ + BAD_OBJECT_SHA1, + /** (ERROR) A commit object has a bad parent sha1. */ + BAD_PARENT_SHA1, + /** (ERROR) A tree has an invalid format. */ + BAD_TREE_SHA1, + /** (ERROR) Author is missing. */ + MISSING_AUTHOR, + /** (ERROR) Committer is missing. */ + MISSING_COMMITTER, + /** (ERROR) Missing object line in tag object. */ + MISSING_OBJECT, + /** (ERROR) Missing tree line in a commit object. */ + MISSING_TREE, + /** (ERROR) Missing type line in a tag object. */ + MISSING_TYPE_ENTRY, + /** (ERROR) Missing tag line in a tag object. */ + MISSING_TAG_ENTRY, + /** (ERROR) Invalid date format in an author/committer line. */ + BAD_DATE, + /** (ERROR) Invalid email format in an author/committer line. */ + BAD_EMAIL, + /** (ERROR) Found an invalid time zone in an author/committer line. */ + BAD_TIMEZONE, + /** (ERROR) Email is missing in an author/committer line. */ + MISSING_EMAIL, + /** (ERROR) Missing space before date in an author/committer line. */ + MISSING_SPACE_BEFORE_DATE, + /** + * (ERROR) A non-blob found at .gitmodules. + * @since 5.2 + */ + GITMODULES_BLOB, + /** + * (ERROR) The .gitmodules file is too large to parse. + * @since 5.2 + */ + GITMODULES_LARGE, + /** + * (ERROR) A submodule name is invalid. + * @since 5.2 + */ + GITMODULES_NAME, + /** + * (INFO) Could not parse .gitmodules blob. + * @since 5.2 + */ + GITMODULES_PARSE, + /** + * (ERROR) .gitmodules path is invalid. + * @since 5.2 + */ + GITMODULES_PATH, + /** + * (ERROR) .gitmodules is a symlink. + * @since 5.2 + */ + GITMODULES_SYMLINK, + /** + * (ERROR) Found an invalid submodule url. + * @since 5.2 + */ + GITMODULES_URL, + /** (ERROR) Found an unknown object type. */ + UNKNOWN_TYPE, // These are unique to JGit. - /***/ WIN32_BAD_NAME, - /***/ BAD_UTF8; - // @formatter:on - - /** @return camelCaseVersion of the name. */ + /** (ERROR) Windows: Invalid name */ + WIN32_BAD_NAME, + /** (ERROR) Byte sequence is not a valid UTF-8 character */ + BAD_UTF8; + + /** + * Get camelCaseVersion of the name + * + * @return camelCaseVersion of the name. + */ public String getMessageId() { String n = name(); StringBuilder r = new StringBuilder(n.length()); @@ -1065,6 +1122,7 @@ public class ObjectChecker { * * @return true if the filename in buf could be a ".gitmodules" file * @throws CorruptObjectException + * if an object is corrupt */ private boolean isGitmodules(byte[] buf, int start, int end, @Nullable AnyObjectId id) throws CorruptObjectException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java index a39766cbd0..a8cc9a8529 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -77,9 +77,12 @@ public abstract class ObjectDatabase implements AutoCloseable { public abstract ObjectReader newReader(); /** + * Get the shallow commits of the current repository + * * @return the shallow commits of the current repository * - * @throws IOException the database could not be read + * @throws IOException + * the database could not be read * * @since 6.3 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java index 269049f4a2..1b455b974d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.nio.ByteBuffer; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.InvalidObjectIdException; @@ -152,6 +153,22 @@ public class ObjectId extends AnyObjectId implements Serializable { } /** + * Convert an ObjectId from raw binary representation + * + * @param bb + * a bytebuffer with the objectid encoded as 5 consecutive ints. + * This is the reverse of {@link ObjectId#copyRawTo(ByteBuffer)} + * + * @return the converted object id. + * + * @since 7.0 + */ + public static final ObjectId fromRaw(ByteBuffer bb) { + return new ObjectId(bb.getInt(), bb.getInt(), bb.getInt(), bb.getInt(), + bb.getInt()); + } + + /** * Convert an ObjectId from raw binary representation. * * @param is @@ -262,7 +279,6 @@ public class ObjectId extends AnyObjectId implements Serializable { w5 = src.w5; } - /** {@inheritDoc} */ @Override public ObjectId toObjectId() { return this; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java index 29039097f1..a74fddff42 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java @@ -128,8 +128,11 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> * <p> * An existing mapping for <b>must not</b> be in this map. Callers must * first call {@link #get(AnyObjectId)} to verify there is no current - * mapping prior to adding a new mapping, or use {@link #addIfAbsent(Entry)}. + * mapping prior to adding a new mapping, or use + * {@link #addIfAbsent(Entry)}. * + * @param <Q> + * type of values * @param newValue * the object to store. */ @@ -157,6 +160,8 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> * boolean wasNew = map.addIfAbsent(obj) == obj; * </pre> * + * @param <Q> + * type of values * @param newValue * the object to store. * @return {@code newValue} if stored, or the prior value already stored and @@ -199,7 +204,6 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> return size == 0; } - /** {@inheritDoc} */ @Override public Iterator<V> iterator() { return new Iterator<>() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java index a04ca6890c..c3c58372b1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java @@ -204,41 +204,35 @@ public abstract class ObjectIdRef implements Ref { this.updateIndex = updateIndex; } - /** {@inheritDoc} */ @Override @NonNull public String getName() { return name; } - /** {@inheritDoc} */ @Override public boolean isSymbolic() { return false; } - /** {@inheritDoc} */ @Override @NonNull public Ref getLeaf() { return this; } - /** {@inheritDoc} */ @Override @NonNull public Ref getTarget() { return this; } - /** {@inheritDoc} */ @Override @Nullable public ObjectId getObjectId() { return objectId; } - /** {@inheritDoc} */ @Override @NonNull public Storage getStorage() { @@ -257,7 +251,6 @@ public abstract class ObjectIdRef implements Ref { return updateIndex; } - /** {@inheritDoc} */ @NonNull @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java index 0e015b658b..d4acfd4714 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java @@ -96,6 +96,8 @@ public class ObjectIdSubclassMap<V extends ObjectId> * mapping prior to adding a new mapping, or use * {@link #addIfAbsent(ObjectId)}. * + * @param <Q> + * type of values * @param newValue * the object to store. */ @@ -117,6 +119,8 @@ public class ObjectIdSubclassMap<V extends ObjectId> * boolean wasNew = map.addIfAbsent(obj) == obj; * </pre> * + * @param <Q> + * type of values * @param newValue * the object to store. * @return {@code newValue} if stored, or the prior value already stored and @@ -162,7 +166,6 @@ public class ObjectIdSubclassMap<V extends ObjectId> return size == 0; } - /** {@inheritDoc} */ @Override public Iterator<V> iterator() { return new Iterator<>() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java index 000899d767..f6c52e292a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java @@ -66,7 +66,11 @@ public abstract class ObjectInserter implements AutoCloseable { /** Wraps a delegate ObjectInserter. */ public abstract static class Filter extends ObjectInserter { - /** @return delegate ObjectInserter to handle all processing. */ + /** + * Get delegate ObjectInserter to handle all processing + * + * @return delegate ObjectInserter to handle all processing. + */ protected abstract ObjectInserter delegate(); @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java index 67b8a2c249..d0564265dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java @@ -302,6 +302,8 @@ public abstract class ObjectLoader { */ public abstract static class Filter extends ObjectLoader { /** + * Get delegate ObjectLoader to handle all processing. + * * @return delegate ObjectLoader to handle all processing. * @since 4.10 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index 69b2b5104e..b916d1e8e9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -245,12 +245,15 @@ public abstract class ObjectReader implements AutoCloseable { * * @return IDs of shallow commits * @throws java.io.IOException + * if an error occurred */ public abstract Set<ObjectId> getShallowCommits() throws IOException; /** * Asynchronous object opening. * + * @param <T> + * type of {@code ObjectId} * @param objectIds * objects to open from the object store. The supplied collection * must not be modified until the queue has finished. @@ -370,6 +373,8 @@ public abstract class ObjectReader implements AutoCloseable { /** * Asynchronous object size lookup. * + * @param <T> + * type of {@code ObjectId} * @param objectIds * objects to get the size of from the object store. The supplied * collection must not be modified until the queue has finished. @@ -578,6 +583,8 @@ public abstract class ObjectReader implements AutoCloseable { */ public abstract static class Filter extends ObjectReader { /** + * Get delegate ObjectReader to handle all processing + * * @return delegate ObjectReader to handle all processing. * @since 4.4 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java index 93710299b4..50f4a83b93 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java @@ -12,10 +12,15 @@ package org.eclipse.jgit.lib; +import static java.time.ZoneOffset.UTC; + import java.io.Serializable; -import java.text.SimpleDateFormat; +import java.time.DateTimeException; import java.time.Instant; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -40,7 +45,9 @@ public class PersonIdent implements Serializable { * timezone offset as in {@link #getTimeZoneOffset()}. * @return time zone object for the given offset. * @since 4.1 + * @deprecated use {@link #getZoneId(int)} instead */ + @Deprecated(since = "7.2") public static TimeZone getTimeZone(int tzOffset) { StringBuilder tzId = new StringBuilder(8); tzId.append("GMT"); //$NON-NLS-1$ @@ -49,6 +56,21 @@ public class PersonIdent implements Serializable { } /** + * Translate a minutes offset into a ZoneId + * + * @param tzOffset as minutes east of UTC + * @return a ZoneId for this offset (UTC if invalid) + * @since 7.1 + */ + public static ZoneId getZoneId(int tzOffset) { + try { + return ZoneOffset.ofHoursMinutes(tzOffset / 60, tzOffset % 60); + } catch (DateTimeException e) { + return UTC; + } + } + + /** * Format a timezone offset. * * @param r @@ -121,13 +143,17 @@ public class PersonIdent implements Serializable { } } + // Write offsets as [+-]HHMM + private static final DateTimeFormatter OFFSET_FORMATTER = DateTimeFormatter + .ofPattern("Z", Locale.US); //$NON-NLS-1$ + private final String name; private final String emailAddress; - private final long when; + private final Instant when; - private final int tzOffset; + private final ZoneId tzOffset; /** * Creates new PersonIdent from config info in repository, with current time. @@ -160,7 +186,7 @@ public class PersonIdent implements Serializable { * a {@link java.lang.String} object. */ public PersonIdent(String aName, String aEmailAddress) { - this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime()); + this(aName, aEmailAddress, SystemReader.getInstance().now()); } /** @@ -177,7 +203,7 @@ public class PersonIdent implements Serializable { */ public PersonIdent(String aName, String aEmailAddress, ProposedTimestamp when) { - this(aName, aEmailAddress, when.millis()); + this(aName, aEmailAddress, when.instant()); } /** @@ -189,8 +215,25 @@ public class PersonIdent implements Serializable { * local time * @param tz * time zone + * @deprecated Use {@link #PersonIdent(PersonIdent, Instant, ZoneId)} instead. */ + @Deprecated(since = "7.1") public PersonIdent(PersonIdent pi, Date when, TimeZone tz) { + this(pi.getName(), pi.getEmailAddress(), when.toInstant(), tz.toZoneId()); + } + + /** + * Copy a PersonIdent, but alter the clone's time stamp + * + * @param pi + * original {@link org.eclipse.jgit.lib.PersonIdent} + * @param when + * local time + * @param tz + * time zone offset + * @since 7.1 + */ + public PersonIdent(PersonIdent pi, Instant when, ZoneId tz) { this(pi.getName(), pi.getEmailAddress(), when, tz); } @@ -202,9 +245,12 @@ public class PersonIdent implements Serializable { * original {@link org.eclipse.jgit.lib.PersonIdent} * @param aWhen * local time + * @deprecated Use the variant with an Instant instead */ + @Deprecated(since = "7.1") public PersonIdent(PersonIdent pi, Date aWhen) { - this(pi.getName(), pi.getEmailAddress(), aWhen.getTime(), pi.tzOffset); + this(pi.getName(), pi.getEmailAddress(), aWhen.toInstant(), + pi.tzOffset); } /** @@ -218,7 +264,7 @@ public class PersonIdent implements Serializable { * @since 6.1 */ public PersonIdent(PersonIdent pi, Instant aWhen) { - this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset); + this(pi.getName(), pi.getEmailAddress(), aWhen, pi.tzOffset); } /** @@ -230,11 +276,12 @@ public class PersonIdent implements Serializable { * local time stamp * @param aTZ * time zone + * @deprecated Use the variant with Instant and ZoneId instead */ + @Deprecated(since = "7.1") public PersonIdent(final String aName, final String aEmailAddress, final Date aWhen, final TimeZone aTZ) { - this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen - .getTime()) / (60 * 1000)); + this(aName, aEmailAddress, aWhen.toInstant(), aTZ.toZoneId()); } /** @@ -252,10 +299,16 @@ public class PersonIdent implements Serializable { */ public PersonIdent(final String aName, String aEmailAddress, Instant aWhen, ZoneId zoneId) { - this(aName, aEmailAddress, aWhen.toEpochMilli(), - TimeZone.getTimeZone(zoneId) - .getOffset(aWhen - .toEpochMilli()) / (60 * 1000)); + if (aName == null) + throw new IllegalArgumentException( + JGitText.get().personIdentNameNonNull); + if (aEmailAddress == null) + throw new IllegalArgumentException( + JGitText.get().personIdentEmailNonNull); + name = aName; + emailAddress = aEmailAddress; + when = aWhen; + tzOffset = zoneId; } /** @@ -267,15 +320,18 @@ public class PersonIdent implements Serializable { * local time stamp * @param aTZ * time zone + * @deprecated Use the variant with Instant and ZoneId instead */ + @Deprecated(since = "7.1") public PersonIdent(PersonIdent pi, long aWhen, int aTZ) { - this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ); + this(pi.getName(), pi.getEmailAddress(), Instant.ofEpochMilli(aWhen), + getZoneId(aTZ)); } private PersonIdent(final String aName, final String aEmailAddress, - long when) { + Instant when) { this(aName, aEmailAddress, when, SystemReader.getInstance() - .getTimezone(when)); + .getTimeZoneAt(when)); } private PersonIdent(UserConfig config) { @@ -298,19 +354,12 @@ public class PersonIdent implements Serializable { * local time stamp * @param aTZ * time zone + * @deprecated Use the variant with Instant and ZoneId instead */ + @Deprecated(since = "7.1") public PersonIdent(final String aName, final String aEmailAddress, final long aWhen, final int aTZ) { - if (aName == null) - throw new IllegalArgumentException( - JGitText.get().personIdentNameNonNull); - if (aEmailAddress == null) - throw new IllegalArgumentException( - JGitText.get().personIdentEmailNonNull); - name = aName; - emailAddress = aEmailAddress; - when = aWhen; - tzOffset = aTZ; + this(aName, aEmailAddress, Instant.ofEpochMilli(aWhen), getZoneId(aTZ)); } /** @@ -335,9 +384,12 @@ public class PersonIdent implements Serializable { * Get timestamp * * @return timestamp + * + * @deprecated Use getWhenAsInstant instead */ + @Deprecated(since = "7.1") public Date getWhen() { - return new Date(when); + return Date.from(when); } /** @@ -347,16 +399,19 @@ public class PersonIdent implements Serializable { * @since 6.1 */ public Instant getWhenAsInstant() { - return Instant.ofEpochMilli(when); + return when; } /** * Get this person's declared time zone * * @return this person's declared time zone; null if time zone is unknown. + * + * @deprecated Use getZoneId instead */ + @Deprecated(since = "7.1") public TimeZone getTimeZone() { - return getTimeZone(tzOffset); + return TimeZone.getTimeZone(tzOffset); } /** @@ -366,7 +421,17 @@ public class PersonIdent implements Serializable { * @since 6.1 */ public ZoneId getZoneId() { - return getTimeZone().toZoneId(); + return tzOffset; + } + + /** + * Return the offset in this timezone at the specific time + * + * @return the offset + * @since 7.1 + */ + public ZoneOffset getZoneOffset() { + return tzOffset.getRules().getOffset(when); } /** @@ -374,9 +439,11 @@ public class PersonIdent implements Serializable { * * @return this person's declared time zone as minutes east of UTC. If the * timezone is to the west of UTC it is negative. + * @deprecated Use {@link #getZoneOffset()} and read minutes from there */ + @Deprecated(since = "7.1") public int getTimeZoneOffset() { - return tzOffset; + return getZoneOffset().getTotalSeconds() / 60; } /** @@ -388,18 +455,19 @@ public class PersonIdent implements Serializable { public int hashCode() { int hc = getEmailAddress().hashCode(); hc *= 31; - hc += (int) (when / 1000L); + hc += when.hashCode(); return hc; } - /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (o instanceof PersonIdent) { final PersonIdent p = (PersonIdent) o; return getName().equals(p.getName()) && getEmailAddress().equals(p.getEmailAddress()) - && when / 1000L == p.when / 1000L; + // commmit timestamps are stored with 1 second precision + && when.truncatedTo(ChronoUnit.SECONDS) + .equals(p.when.truncatedTo(ChronoUnit.SECONDS)); } return false; } @@ -415,30 +483,26 @@ public class PersonIdent implements Serializable { r.append(" <"); //$NON-NLS-1$ appendSanitized(r, getEmailAddress()); r.append("> "); //$NON-NLS-1$ - r.append(when / 1000); + r.append(when.toEpochMilli() / 1000); r.append(' '); - appendTimezone(r, tzOffset); + r.append(OFFSET_FORMATTER.format(getZoneOffset())); return r.toString(); } - /** {@inheritDoc} */ @Override @SuppressWarnings("nls") public String toString() { final StringBuilder r = new StringBuilder(); - final SimpleDateFormat dtfmt; - dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US); - dtfmt.setTimeZone(getTimeZone()); - + DateTimeFormatter dtfmt = DateTimeFormatter + .ofPattern("EEE MMM d HH:mm:ss yyyy Z", Locale.US) //$NON-NLS-1$ + .withZone(tzOffset); r.append("PersonIdent["); r.append(getName()); r.append(", "); r.append(getEmailAddress()); r.append(", "); - r.append(dtfmt.format(Long.valueOf(when))); + r.append(dtfmt.format(when)); r.append("]"); - return r.toString(); } } - diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java index a4cd1d6894..b036a0b6a1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java @@ -17,7 +17,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.lib.RebaseTodoLine.Action; @@ -55,13 +55,14 @@ public class RebaseTodoFile { * <code>true</code> if also comments should be reported * @return the list of steps * @throws java.io.IOException + * if an IO error occurred */ public List<RebaseTodoLine> readRebaseTodo(String path, boolean includeComments) throws IOException { byte[] buf = IO.readFully(new File(repo.getDirectory(), path)); int ptr = 0; int tokenBegin = 0; - List<RebaseTodoLine> r = new LinkedList<>(); + List<RebaseTodoLine> r = new ArrayList<>(); while (ptr < buf.length) { tokenBegin = ptr; ptr = RawParseUtils.nextLF(buf, ptr); @@ -126,8 +127,11 @@ public class RebaseTodoFile { * Skip leading space, tab, CR and LF characters * * @param buf + * byte buffer * @param tokenBegin + * index of token begin * @param lineEnd + * index of line end * @return the token within the range of the given {@code buf} that doesn't * need to be skipped, {@code -1} if no such token found within the * range (i.e. empty line) @@ -193,6 +197,7 @@ public class RebaseTodoFile { * @param append * whether to append to an existing file or to write a new file * @throws java.io.IOException + * if an IO error occurred */ public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, boolean append) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java index 8b778497fc..082b324d1a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java @@ -57,6 +57,8 @@ public class RebaseTodoLine { } /** + * Get full action token name + * * @return full action token name */ public String toToken() { @@ -69,7 +71,10 @@ public class RebaseTodoLine { } /** + * Parse a token + * * @param token + * token to parse * @return the Action */ public static Action parse(String token) { @@ -245,7 +250,6 @@ public class RebaseTodoLine { return comment; } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java index 218c0a8e8e..9c374760b0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java @@ -78,6 +78,8 @@ public interface Ref { } /** + * Whether this storage has a loose file + * * @return true if this storage has a loose file. */ public boolean isLoose() { @@ -85,6 +87,8 @@ public interface Ref { } /** + * Whether this storage is inside the packed file + * * @return true if this storage is inside the packed file. */ public boolean isPacked() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java index 3ab649341f..c4c0001d3e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java @@ -27,7 +27,6 @@ public class RefComparator implements Comparator<Ref> { /** Singleton instance of RefComparator */ public static final RefComparator INSTANCE = new RefComparator(); - /** {@inheritDoc} */ @Override public int compare(Ref o1, Ref o2) { return compareTo(o1, o2); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index 98089fb8fd..49d5224325 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -26,6 +26,7 @@ import java.util.stream.Stream; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.PackRefsCommand; /** * Abstraction of name to {@link org.eclipse.jgit.lib.ObjectId} mapping. @@ -136,6 +137,7 @@ public abstract class RefDatabase { * with the passed ref name; empty collection when there are no * conflicts * @throws java.io.IOException + * if an error occurred * @since 2.3 * @see #isNameConflicting(String) */ @@ -159,7 +161,7 @@ public abstract class RefDatabase { if (existing.startsWith(prefix)) conflicting.add(existing); - return conflicting; + return Collections.unmodifiableList(conflicting); } /** @@ -237,23 +239,6 @@ public abstract class RefDatabase { } /** - * Compatibility synonym for {@link #findRef(String)}. - * - * @param name - * the name of the reference. May be a short name which must be - * searched for using the standard {@link #SEARCH_PATH}. - * @return the reference (if it exists); else {@code null}. - * @throws IOException - * the reference space cannot be accessed. - * @deprecated Use {@link #findRef(String)} instead. - */ - @Deprecated - @Nullable - public final Ref getRef(String name) throws IOException { - return findRef(name); - } - - /** * Read a single reference. * <p> * Aside from taking advantage of {@link #SEARCH_PATH}, this method may be @@ -371,6 +356,40 @@ public abstract class RefDatabase { } /** + * Get the reflog reader + * + * @param refName + * a {@link java.lang.String} object. + * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied + * refname, or {@code null} if the named ref does not exist. + * @throws java.io.IOException + * the ref could not be accessed. + * @since 7.2 + */ + @Nullable + public ReflogReader getReflogReader(String refName) throws IOException { + Ref ref = exactRef(refName); + if (ref == null) { + return null; + } + return getReflogReader(ref); + } + + /** + * Get the reflog reader. + * + * @param ref + * a Ref + * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref. + * @throws IOException + * if an IO error occurred + * @since 7.2 + */ + @NonNull + public abstract ReflogReader getReflogReader(@NonNull Ref ref) + throws IOException; + + /** * Get a section of the reference namespace. * * @param prefix @@ -440,7 +459,7 @@ public abstract class RefDatabase { * @param excludes * strings that names of refs can't start with; may be empty. * @return immutable list of refs whose names start with {@code prefix} and - * none of the strings in {@code exclude}. + * none of the strings in {@code excludes}. * @throws java.io.IOException * the reference space cannot be accessed. * @since 5.11 @@ -609,4 +628,22 @@ public abstract class RefDatabase { } return null; } + + /** + * Optimize pack ref storage. + * + * @param pm + * a progress monitor + * + * @param packRefs + * {@link PackRefsCommand} to control ref packing behavior + * + * @throws java.io.IOException + * if an IO error occurred + * @since 7.1 + */ + public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs) + throws IOException { + // nothing + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java index cdd4e4ba75..e8821d0cbb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java @@ -122,6 +122,7 @@ public abstract class RefRename { * * @return the result of the new ref update * @throws java.io.IOException + * if an IO error occurred */ public Result rename() throws IOException { try { @@ -138,6 +139,7 @@ public abstract class RefRename { * * @return the result of the rename operation. * @throws java.io.IOException + * if an IO error occurred */ protected abstract Result doRename() throws IOException; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java index d1be63b966..d113243bea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java @@ -115,7 +115,6 @@ public abstract class RefUpdate { /** * The ref was renamed from another name - * <p> */ RENAMED, @@ -245,6 +244,7 @@ public abstract class RefUpdate { * a {@link org.eclipse.jgit.lib.RefUpdate.Result} object. * @return {@code result} * @throws java.io.IOException + * if an IO error occurred */ protected abstract Result doUpdate(Result desiredResult) throws IOException; @@ -255,6 +255,7 @@ public abstract class RefUpdate { * a {@link org.eclipse.jgit.lib.RefUpdate.Result} object. * @return {@code result} * @throws java.io.IOException + * if an IO error occurred */ protected abstract Result doDelete(Result desiredResult) throws IOException; @@ -265,6 +266,7 @@ public abstract class RefUpdate { * a {@link java.lang.String} object. * @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} on success. * @throws java.io.IOException + * if an IO error occurred */ protected abstract Result doLink(String target) throws IOException; @@ -612,6 +614,7 @@ public abstract class RefUpdate { * * @return the result status of the delete. * @throws java.io.IOException + * if an IO error occurred */ public Result delete() throws IOException { try (RevWalk rw = new RevWalk(getRepository())) { @@ -628,6 +631,7 @@ public abstract class RefUpdate { * the merge test. The walk will be reset to perform the test. * @return the result status of the delete. * @throws java.io.IOException + * if an IO error occurred */ public Result delete(RevWalk walk) throws IOException { final String myName = detachingSymbolicRef @@ -668,6 +672,7 @@ public abstract class RefUpdate { * @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} or * {@link org.eclipse.jgit.lib.RefUpdate.Result#FORCED} on success. * @throws java.io.IOException + * if an IO error occurred */ public Result link(String target) throws IOException { if (!target.startsWith(Constants.R_REFS)) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java index d2c3f9de68..41917f8b57 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java @@ -177,6 +177,7 @@ public abstract class RefWriter { * @param content * byte content of file to be written. * @throws java.io.IOException + * if an IO error occurred */ protected abstract void writeFile(String file, byte[] content) throws IOException; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java index 1e6a661572..5233bfc109 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java @@ -25,6 +25,7 @@ public interface ReflogReader { * * @return the latest reflog entry, or null if no log * @throws java.io.IOException + * if an IO error occurred */ ReflogEntry getLastEntry() throws IOException; @@ -33,6 +34,7 @@ public interface ReflogReader { * * @return all reflog entries in reverse order * @throws java.io.IOException + * if an IO error occurred */ List<ReflogEntry> getReverseEntries() throws IOException; @@ -40,9 +42,11 @@ public interface ReflogReader { * Get specific entry in the reflog relative to the last entry which is * considered entry zero. * - * @param number a int. + * @param number + * a int. * @return reflog entry or null if not found * @throws java.io.IOException + * if an IO error occurred */ ReflogEntry getReverseEntry(int number) throws IOException; @@ -53,6 +57,7 @@ public interface ReflogReader { * max number of entries to read * @return all reflog entries in reverse order * @throws java.io.IOException + * if an IO error occurred */ List<ReflogEntry> getReverseEntries(int max) throws IOException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index db2571c673..c9dc6da4ba 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -15,8 +15,8 @@ package org.eclipse.jgit.lib; -import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX; import java.io.BufferedOutputStream; import java.io.File; @@ -26,17 +26,21 @@ import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.LinkOption; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import org.eclipse.jgit.annotations.NonNull; @@ -113,9 +117,12 @@ public abstract class Repository implements AutoCloseable { final AtomicLong closedAt = new AtomicLong(); - /** Metadata directory holding the repository's critical files. */ + /** $GIT_DIR: metadata directory holding the repository's critical files. */ private final File gitDir; + /** $GIT_COMMON_DIR: metadata directory holding the common repository's critical files. */ + private final File gitCommonDir; + /** File abstraction used to resolve paths. */ private final FS fs; @@ -129,6 +136,8 @@ public abstract class Repository implements AutoCloseable { private final String initialBranch; + private final AtomicReference<Boolean> caseInsensitiveWorktree = new AtomicReference<>(); + /** * Initialize a new repository instance. * @@ -137,6 +146,7 @@ public abstract class Repository implements AutoCloseable { */ protected Repository(BaseRepositoryBuilder options) { gitDir = options.getGitDir(); + gitCommonDir = options.getGitCommonDir(); fs = options.getFS(); workTree = options.getWorkTree(); indexFile = options.getIndexFile(); @@ -175,6 +185,7 @@ public abstract class Repository implements AutoCloseable { * the same as {@code create(false)}. * * @throws java.io.IOException + * if an IO error occurred * @see #create(boolean) */ public void create() throws IOException { @@ -219,6 +230,16 @@ public abstract class Repository implements AutoCloseable { public abstract String getIdentifier(); /** + * Get common dir. + * + * @return $GIT_COMMON_DIR: local common metadata directory; + * @since 7.0 + */ + public File getCommonDirectory() { + return gitCommonDir; + } + + /** * Get the object database which stores this repository's data. * * @return the object database which stores this repository's data. @@ -292,25 +313,6 @@ public abstract class Repository implements AutoCloseable { } /** - * Whether the specified object is stored in this repo or any of the known - * shared repositories. - * - * @param objectId - * a {@link org.eclipse.jgit.lib.AnyObjectId} object. - * @return true if the specified object is stored in this repo or any of the - * known shared repositories. - * @deprecated use {@code getObjectDatabase().has(objectId)} - */ - @Deprecated - public boolean hasObject(AnyObjectId objectId) { - try { - return getObjectDatabase().has(objectId); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - /** * Open an object from this repository. * <p> * This is a one-shot call interface which may be faster than allocating a @@ -484,11 +486,14 @@ public abstract class Repository implements AutoCloseable { * Thus this method can be used to process an expression to a method that * expects a branch or revision id. * - * @param revstr a {@link java.lang.String} object. + * @param revstr + * a {@link java.lang.String} object. * @return object id or ref name from resolved expression or {@code null} if * given expression cannot be resolved * @throws org.eclipse.jgit.errors.AmbiguousObjectException + * if a shortened ObjectId was ambiguous * @throws java.io.IOException + * if an IO error occurred */ @Nullable public String simplify(String revstr) @@ -968,7 +973,6 @@ public abstract class Repository implements AutoCloseable { getRefDatabase().close(); } - /** {@inheritDoc} */ @Override @NonNull public String toString() { @@ -999,6 +1003,7 @@ public abstract class Repository implements AutoCloseable { * {@code null} if the repository is corrupt and has no HEAD * reference. * @throws java.io.IOException + * if an IO error occurred */ @Nullable public String getFullBranch() throws IOException { @@ -1027,6 +1032,7 @@ public abstract class Repository implements AutoCloseable { * in hex format if the current branch is detached, or {@code null} * if the repository is corrupt and has no HEAD reference. * @throws java.io.IOException + * if an IO error occurred */ @Nullable public String getBranch() throws IOException { @@ -1056,6 +1062,7 @@ public abstract class Repository implements AutoCloseable { * * @return unmodifiable collection of other known objects. * @throws IOException + * if an IO error occurred */ @NonNull public Set<ObjectId> getAdditionalHaves() throws IOException { @@ -1066,11 +1073,12 @@ public abstract class Repository implements AutoCloseable { * Get a ref by name. * * @param name - * the name of the ref to lookup. Must not be a short-hand - * form; e.g., "master" is not automatically expanded to + * the name of the ref to lookup. Must not be a short-hand form; + * e.g., "master" is not automatically expanded to * "refs/heads/master". * @return the Ref with the given name, or {@code null} if it does not exist * @throws java.io.IOException + * if an IO error occurred * @since 4.2 */ @Nullable @@ -1087,6 +1095,7 @@ public abstract class Repository implements AutoCloseable { * "refs/heads/master" if "refs/heads/master" already exists. * @return the Ref with the given name, or {@code null} if it does not exist * @throws java.io.IOException + * if an IO error occurred * @since 4.2 */ @Nullable @@ -1142,11 +1151,9 @@ public abstract class Repository implements AutoCloseable { * new Ref object representing the same data as Ref, but isPeeled() * will be true and getPeeledObjectId will contain the peeled object * (or null). - * @deprecated use {@code getRefDatabase().peel(ref)} instead. */ - @Deprecated @NonNull - public Ref peel(Ref ref) { + private Ref peel(Ref ref) { try { return getRefDatabase().peel(ref); } catch (IOException e) { @@ -1162,6 +1169,7 @@ public abstract class Repository implements AutoCloseable { * * @return a map with all objects referenced by a peeled ref. * @throws IOException + * if an IO error occurred */ @NonNull public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() @@ -1575,10 +1583,45 @@ public abstract class Repository implements AutoCloseable { } /** + * Tells whether the work tree is on a case-insensitive file system. + * + * @return {@code true} if the work tree is case-insensitive; {@code false} + * otherwise + * @throws NoWorkTreeException + * if the repository is bare + * @since 7.2 + */ + public boolean isWorkTreeCaseInsensitive() throws NoWorkTreeException { + Boolean flag = caseInsensitiveWorktree.get(); + if (flag == null) { + File directory = getWorkTree(); + // See if we can find ".git" also as ".GIT". + File dotGit = new File(directory, Constants.DOT_GIT); + if (Files.exists(dotGit.toPath(), LinkOption.NOFOLLOW_LINKS)) { + dotGit = new File(directory, + Constants.DOT_GIT.toUpperCase(Locale.ROOT)); + flag = Boolean.valueOf(Files.exists(dotGit.toPath(), + LinkOption.NOFOLLOW_LINKS)); + } else { + // Fall back to a mostly sane default. On Mac, HFS+ and APFS + // partitions are case-insensitive by default but can be + // configured to be case-sensitive. + SystemReader system = SystemReader.getInstance(); + flag = Boolean.valueOf(system.isWindows() || system.isMacOS()); + } + if (!caseInsensitiveWorktree.compareAndSet(null, flag)) { + flag = caseInsensitiveWorktree.get(); + } + } + return flag.booleanValue(); + } + + /** * Force a scan for changed refs. Fires an IndexChangedEvent(false) if * changes are detected. * * @throws java.io.IOException + * if an IO error occurred */ public abstract void scanForRepoChanges() throws IOException; @@ -1689,10 +1732,13 @@ public abstract class Repository implements AutoCloseable { * @throws java.io.IOException * the ref could not be accessed. * @since 3.0 + * @deprecated use {@code #getRefDatabase().getReflogReader(String)} instead */ + @Deprecated(since = "7.2") @Nullable - public abstract ReflogReader getReflogReader(String refName) - throws IOException; + public ReflogReader getReflogReader(String refName) throws IOException { + return getRefDatabase().getReflogReader(refName); + } /** * Get the reflog reader. Subclasses should override this method and provide @@ -1700,14 +1746,17 @@ public abstract class Repository implements AutoCloseable { * * @param ref * a Ref - * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref, - * or {@code null} if the ref does not exist. + * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref. * @throws IOException + * if an IO error occurred * @since 5.13.2 + * @deprecated use {@code #getRefDatabase().getReflogReader(Ref)} instead */ - public @Nullable ReflogReader getReflogReader(@NonNull Ref ref) + @Deprecated(since = "7.2") + @NonNull + public ReflogReader getReflogReader(@NonNull Ref ref) throws IOException { - return getReflogReader(ref.getName()); + return getRefDatabase().getReflogReader(ref); } /** @@ -1718,6 +1767,7 @@ public abstract class Repository implements AutoCloseable { * @return a String containing the content of the MERGE_MSG file or * {@code null} if this file doesn't exist * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. @@ -1737,6 +1787,7 @@ public abstract class Repository implements AutoCloseable { * the message which should be written or <code>null</code> to * delete the file * @throws java.io.IOException + * if an IO error occurred */ public void writeMergeCommitMsg(String msg) throws IOException { File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG); @@ -1751,6 +1802,7 @@ public abstract class Repository implements AutoCloseable { * @return a String containing the content of the COMMIT_EDITMSG file or * {@code null} if this file doesn't exist * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. @@ -1770,6 +1822,7 @@ public abstract class Repository implements AutoCloseable { * the message which should be written or {@code null} to delete * the file * @throws java.io.IOException + * if an IO error occurred * @since 4.0 */ public void writeCommitEditMsg(String msg) throws IOException { @@ -1786,6 +1839,7 @@ public abstract class Repository implements AutoCloseable { * {@code null} if this file doesn't exist. Also if the file exists * but is empty {@code null} will be returned * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. @@ -1799,7 +1853,7 @@ public abstract class Repository implements AutoCloseable { if (raw == null) return null; - LinkedList<ObjectId> heads = new LinkedList<>(); + List<ObjectId> heads = new ArrayList<>(); for (int p = 0; p < raw.length;) { heads.add(ObjectId.fromString(raw, p)); p = RawParseUtils @@ -1818,6 +1872,7 @@ public abstract class Repository implements AutoCloseable { * a list of commits which IDs should be written to * $GIT_DIR/MERGE_HEAD or <code>null</code> to delete the file * @throws java.io.IOException + * if an IO error occurred */ public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException { writeHeadsFile(heads, Constants.MERGE_HEAD); @@ -1830,6 +1885,7 @@ public abstract class Repository implements AutoCloseable { * doesn't exist. Also if the file exists but is empty {@code null} * will be returned * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. @@ -1854,6 +1910,7 @@ public abstract class Repository implements AutoCloseable { * doesn't exist. Also if the file exists but is empty {@code null} * will be returned * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. @@ -1877,6 +1934,7 @@ public abstract class Repository implements AutoCloseable { * an object id of the cherry commit or <code>null</code> to * delete the file * @throws java.io.IOException + * if an IO error occurred */ public void writeCherryPickHead(ObjectId head) throws IOException { List<ObjectId> heads = (head != null) ? Collections.singletonList(head) @@ -1892,6 +1950,7 @@ public abstract class Repository implements AutoCloseable { * an object id of the revert commit or <code>null</code> to * delete the file * @throws java.io.IOException + * if an IO error occurred */ public void writeRevertHead(ObjectId head) throws IOException { List<ObjectId> heads = (head != null) ? Collections.singletonList(head) @@ -1906,6 +1965,7 @@ public abstract class Repository implements AutoCloseable { * an object id of the original HEAD commit or <code>null</code> * to delete the file * @throws java.io.IOException + * if an IO error occurred */ public void writeOrigHead(ObjectId head) throws IOException { List<ObjectId> heads = head != null ? Collections.singletonList(head) @@ -1920,6 +1980,7 @@ public abstract class Repository implements AutoCloseable { * doesn't exist. Also if the file exists but is empty {@code null} * will be returned * @throws java.io.IOException + * if an IO error occurred * @throws org.eclipse.jgit.errors.NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. @@ -1941,6 +2002,7 @@ public abstract class Repository implements AutoCloseable { * @return a String containing the content of the SQUASH_MSG file or * {@code null} if this file doesn't exist * @throws java.io.IOException + * if an IO error occurred * @throws NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. @@ -1960,6 +2022,7 @@ public abstract class Repository implements AutoCloseable { * the message which should be written or <code>null</code> to * delete the file * @throws java.io.IOException + * if an IO error occurred */ public void writeSquashCommitMsg(String msg) throws IOException { File squashMsgFile = new File(gitDir, Constants.SQUASH_MSG); @@ -1997,9 +2060,11 @@ public abstract class Repository implements AutoCloseable { * Read a file from the git directory. * * @param filename + * the file to read * @return the raw contents or {@code null} if the file doesn't exist or is * empty * @throws IOException + * if an IO error occurred */ private byte[] readGitDirectoryFile(String filename) throws IOException { File file = new File(getDirectory(), filename); @@ -2021,8 +2086,11 @@ public abstract class Repository implements AutoCloseable { * a list of object ids to write or null if the file should be * deleted. * @param filename + * name of the file to write heads to * @throws FileNotFoundException + * if the heads file couldn't be found * @throws IOException + * if an IO error occurred */ private void writeHeadsFile(List<? extends ObjectId> heads, String filename) throws FileNotFoundException, IOException { @@ -2052,6 +2120,7 @@ public abstract class Repository implements AutoCloseable { * <code>true</code> if also comments should be reported * @return the list of steps * @throws java.io.IOException + * if an IO error occurred * @since 3.2 */ @NonNull @@ -2072,6 +2141,7 @@ public abstract class Repository implements AutoCloseable { * @param append * whether to append to an existing file or to write a new file * @throws java.io.IOException + * if an IO error occurred * @since 3.2 */ public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java index 41f291b57b..18366541da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java @@ -382,6 +382,8 @@ public class RepositoryCache { private final FS fs; /** + * Create a pointer to a location on disk. + * * @param directory * exact location of the repository. * @param fs @@ -401,7 +403,11 @@ public class RepositoryCache { } } - /** @return location supplied to the constructor. */ + /** + * Get directory containing the repository database + * + * @return directory containing the repository database. + */ public final File getFile() { return path; } @@ -444,10 +450,21 @@ public class RepositoryCache { * Git directory. */ public static boolean isGitRepository(File dir, FS fs) { - return fs.resolve(dir, Constants.OBJECTS).exists() - && fs.resolve(dir, "refs").exists() //$NON-NLS-1$ - && (fs.resolve(dir, Constants.REFTABLE).exists() - || isValidHead(new File(dir, Constants.HEAD))); + // check if common-dir available or fallback to git-dir + File commonDir; + try { + commonDir = fs.getCommonDir(dir); + } catch (IOException e) { + commonDir = null; + } + if (commonDir == null) { + commonDir = dir; + } + return fs.resolve(commonDir, Constants.OBJECTS).exists() + && fs.resolve(commonDir, "refs").exists() //$NON-NLS-1$ + && (fs.resolve(commonDir, Constants.REFTABLE).exists() + || isValidHead( + new File(commonDir, Constants.HEAD))); } private static boolean isValidHead(File head) { @@ -490,15 +507,31 @@ public class RepositoryCache { * null if there is no suitable match. */ public static File resolve(File directory, FS fs) { - if (isGitRepository(directory, fs)) + // the folder itself + if (isGitRepository(directory, fs)) { return directory; - if (isGitRepository(new File(directory, Constants.DOT_GIT), fs)) - return new File(directory, Constants.DOT_GIT); - - final String name = directory.getName(); - final File parent = directory.getParentFile(); - if (isGitRepository(new File(parent, name + Constants.DOT_GIT_EXT), fs)) - return new File(parent, name + Constants.DOT_GIT_EXT); + } + // the .git subfolder or file (reference) + File dotDir = new File(directory, Constants.DOT_GIT); + if (dotDir.isFile()) { + try { + File refDir = BaseRepositoryBuilder.getSymRef(directory, + dotDir, fs); + if (refDir != null && isGitRepository(refDir, fs)) { + return refDir; + } + } catch (IOException ignored) { + // Continue searching if gitdir ref isn't found + } + } else if (isGitRepository(dotDir, fs)) { + return dotDir; + } + // the folder extended with .git (bare) + File bareDir = new File(directory.getParentFile(), + directory.getName() + Constants.DOT_GIT_EXT); + if (isGitRepository(bareDir, fs)) { + return bareDir; + } return null; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java index 98d719183d..eaa15fbc99 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java @@ -355,27 +355,37 @@ public enum RepositoryState { public abstract boolean canCheckout(); /** + * Whether we can commit + * * @return true if we can commit */ public abstract boolean canCommit(); /** + * Whether reset to another HEAD is considered SAFE + * * @return true if reset to another HEAD is considered SAFE */ public abstract boolean canResetHead(); /** + * Whether amending is considered SAFE + * * @return true if amending is considered SAFE */ public abstract boolean canAmend(); /** + * Whether a rebase is in progress + * * @return true if the repository is currently in a rebase * @since 3.0 */ public abstract boolean isRebasing(); /** + * Get a human readable description of the state + * * @return a human readable description of the state. */ public abstract String getDescription(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java new file mode 100644 index 0000000000..2ce2708cb9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lib; + +import java.io.IOException; +import java.util.Date; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.JGitInternalException; + +/** + * A {@code SignatureVerifier} can verify signatures on git commits and tags. + * + * @since 7.0 + */ +public interface SignatureVerifier { + + /** + * Verifies a given signature for given data. + * + * @param repository + * the {@link Repository} the data comes from. + * @param config + * the {@link GpgConfig} + * @param data + * the signature is for + * @param signatureData + * the ASCII-armored signature + * @return a {@link SignatureVerification} describing the outcome + * @throws IOException + * if the signature cannot be parsed + * @throws JGitInternalException + * if signature verification fails + */ + SignatureVerification verify(@NonNull Repository repository, + @NonNull GpgConfig config, byte[] data, byte[] signatureData) + throws IOException; + + /** + * Retrieves the name of this verifier. This should be a short string + * identifying the engine that verified the signature, like "gpg" if GPG is + * used, or "bc" for a BouncyCastle implementation. + * + * @return the name + */ + @NonNull + String getName(); + + /** + * A {@link SignatureVerifier} may cache public keys to speed up + * verifying signatures on multiple objects. This clears this cache, if any. + */ + void clear(); + + /** + * A {@code SignatureVerification} returns data about a (positively or + * negatively) verified signature. + * + * @param verifierName + * the name of the verifier that created this verification result + * @param creationDate + * date and time the signature was created + * @param signer + * the signer as stored in the signature, or {@code null} if + * unknown + * @param keyFingerprint + * fingerprint of the public key, or {@code null} if unknown + * @param keyUser + * user associated with the key, or {@code null} if unknown + * @param verified + * whether the signature verification was successful + * @param expired + * whether the public key used for this signature verification + * was expired when the signature was created + * @param trustLevel + * the trust level of the public key used to verify the signature + * @param message + * human-readable message giving additional information about the + * outcome of the verification, possibly {@code null} + */ + record SignatureVerification( + String verifierName, + Date creationDate, + String signer, + String keyFingerprint, + String keyUser, + boolean verified, + boolean expired, + @NonNull TrustLevel trustLevel, + String message) { + } + + /** + * The owner's trust in a public key. + */ + enum TrustLevel { + UNKNOWN, NEVER, MARGINAL, FULL, ULTIMATE + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java new file mode 100644 index 0000000000..7844aba3bd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lib; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * A factory for {@link SignatureVerifier}s. + * + * @since 7.0 + */ +public interface SignatureVerifierFactory { + + /** + * Tells what kind of {@link SignatureVerifier} this factory creates. + * + * @return the {@link GpgConfig.GpgFormat} of the signer + */ + @NonNull + GpgConfig.GpgFormat getType(); + + /** + * Creates a new instance of a {@link SignatureVerifier} that can produce + * signatures of type {@link #getType()}. + * + * @return a new {@link SignatureVerifier} + */ + @NonNull + SignatureVerifier create(); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java new file mode 100644 index 0000000000..01c8422b66 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lib; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.util.RawParseUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages the available signers. + * + * @since 7.0 + */ +public final class SignatureVerifiers { + + private static final Logger LOG = LoggerFactory.getLogger(SignatureVerifiers.class); + + private static final byte[] PGP_PREFIX = Constants.GPG_SIGNATURE_PREFIX + .getBytes(StandardCharsets.US_ASCII); + + private static final byte[] X509_PREFIX = Constants.CMS_SIGNATURE_PREFIX + .getBytes(StandardCharsets.US_ASCII); + + private static final byte[] SSH_PREFIX = Constants.SSH_SIGNATURE_PREFIX + .getBytes(StandardCharsets.US_ASCII); + + private static final Map<GpgConfig.GpgFormat, SignatureVerifierFactory> FACTORIES = loadSignatureVerifiers(); + + private static final Map<GpgConfig.GpgFormat, SignatureVerifier> VERIFIERS = new ConcurrentHashMap<>(); + + private static Map<GpgConfig.GpgFormat, SignatureVerifierFactory> loadSignatureVerifiers() { + Map<GpgConfig.GpgFormat, SignatureVerifierFactory> result = new EnumMap<>( + GpgConfig.GpgFormat.class); + try { + for (SignatureVerifierFactory factory : ServiceLoader + .load(SignatureVerifierFactory.class)) { + GpgConfig.GpgFormat format = factory.getType(); + SignatureVerifierFactory existing = result.get(format); + if (existing != null) { + LOG.warn("{}", //$NON-NLS-1$ + MessageFormat.format( + JGitText.get().signatureServiceConflict, + "SignatureVerifierFactory", format, //$NON-NLS-1$ + existing.getClass().getCanonicalName(), + factory.getClass().getCanonicalName())); + } else { + result.put(format, factory); + } + } + } catch (ServiceConfigurationError e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + private SignatureVerifiers() { + // No instantiation + } + + /** + * Retrieves a {@link Signer} that can produce signatures of the given type + * {@code format}. + * + * @param format + * {@link GpgConfig.GpgFormat} the signer must support + * @return a {@link Signer}, or {@code null} if none is available + */ + public static SignatureVerifier get(@NonNull GpgConfig.GpgFormat format) { + return VERIFIERS.computeIfAbsent(format, f -> { + SignatureVerifierFactory factory = FACTORIES.get(format); + if (factory == null) { + return null; + } + return factory.create(); + }); + } + + /** + * Sets a specific signature verifier to use for a specific signature type. + * + * @param format + * signature type to set the {@code verifier} for + * @param verifier + * the {@link SignatureVerifier} to use for signatures of type + * {@code format}; if {@code null}, a default implementation, if + * available, may be used. + */ + public static void set(@NonNull GpgConfig.GpgFormat format, + SignatureVerifier verifier) { + SignatureVerifier previous; + if (verifier == null) { + previous = VERIFIERS.remove(format); + } else { + previous = VERIFIERS.put(format, verifier); + } + if (previous != null) { + previous.clear(); + } + } + + /** + * Verifies the signature on a signed commit or tag. + * + * @param repository + * the {@link Repository} the object is from + * @param config + * the {@link GpgConfig} to use + * @param object + * to verify + * @return a {@link SignatureVerifier.SignatureVerification} describing the + * outcome of the verification, or {@code null} if the object does + * not have a signature of a known type + * @throws IOException + * if an error occurs getting a public key + * @throws org.eclipse.jgit.api.errors.JGitInternalException + * if signature verification fails + */ + @Nullable + public static SignatureVerifier.SignatureVerification verify( + @NonNull Repository repository, @NonNull GpgConfig config, + @NonNull RevObject object) throws IOException { + if (object instanceof RevCommit) { + RevCommit commit = (RevCommit) object; + byte[] signatureData = commit.getRawGpgSignature(); + if (signatureData == null) { + return null; + } + byte[] raw = commit.getRawBuffer(); + // Now remove the GPG signature + byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' }; + int start = RawParseUtils.headerStart(header, raw, 0); + if (start < 0) { + return null; + } + int end = RawParseUtils.nextLfSkippingSplitLines(raw, start); + // start is at the beginning of the header's content + start -= header.length + 1; + // end is on the terminating LF; we need to skip that, too + if (end < raw.length) { + end++; + } + byte[] data = new byte[raw.length - (end - start)]; + System.arraycopy(raw, 0, data, 0, start); + System.arraycopy(raw, end, data, start, raw.length - end); + return verify(repository, config, data, signatureData); + } else if (object instanceof RevTag) { + RevTag tag = (RevTag) object; + byte[] signatureData = tag.getRawGpgSignature(); + if (signatureData == null) { + return null; + } + byte[] raw = tag.getRawBuffer(); + // The signature is just tacked onto the end of the message, which + // is last in the buffer. + byte[] data = Arrays.copyOfRange(raw, 0, + raw.length - signatureData.length); + return verify(repository, config, data, signatureData); + } + return null; + } + + /** + * Verifies a given signature for some give data. + * + * @param repository + * the {@link Repository} the object is from + * @param config + * the {@link GpgConfig} to use + * @param data + * to verify the signature of + * @param signature + * the given signature of the {@code data} + * @return a {@link SignatureVerifier.SignatureVerification} describing the + * outcome of the verification, or {@code null} if the signature + * type is unknown + * @throws IOException + * if an error occurs getting a public key + * @throws org.eclipse.jgit.api.errors.JGitInternalException + * if signature verification fails + */ + @Nullable + public static SignatureVerifier.SignatureVerification verify( + @NonNull Repository repository, @NonNull GpgConfig config, + byte[] data, byte[] signature) throws IOException { + GpgConfig.GpgFormat format = getFormat(signature); + if (format == null) { + return null; + } + SignatureVerifier verifier = get(format); + if (verifier == null) { + return null; + } + return verifier.verify(repository, config, data, signature); + } + + /** + * Determines the type of a given signature. + * + * @param signature + * to get the type of + * @return the signature type, or {@code null} if unknown + */ + @Nullable + public static GpgConfig.GpgFormat getFormat(byte[] signature) { + if (RawParseUtils.match(signature, 0, PGP_PREFIX) > 0) { + return GpgConfig.GpgFormat.OPENPGP; + } + if (RawParseUtils.match(signature, 0, X509_PREFIX) > 0) { + return GpgConfig.GpgFormat.X509; + } + if (RawParseUtils.match(signature, 0, SSH_PREFIX) > 0) { + return GpgConfig.GpgFormat.SSH; + } + return null; + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java new file mode 100644 index 0000000000..3bb7464d08 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lib; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; +import org.eclipse.jgit.transport.CredentialsProvider; + +/** + * Creates signatures for Git objects. + * + * @since 7.0 + */ +public interface Signer { + + /** + * Signs the specified object. + * + * <p> + * Implementors should obtain the payload for signing from the specified + * object via {@link ObjectBuilder#build()} and create a proper + * {@link GpgSignature}. The generated signature is set on the specified + * {@code object} (see {@link ObjectBuilder#setGpgSignature(GpgSignature)}). + * </p> + * <p> + * Any existing signature on the object must be discarded prior obtaining + * the payload via {@link ObjectBuilder#build()}. + * </p> + * + * @param repository + * {@link Repository} the object belongs to + * @param config + * GPG settings from the git config + * @param object + * the object to sign (must not be {@code null} and must be + * complete to allow proper calculation of payload) + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param signingKey + * if non-{@code null} overrides the signing key from the config + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + * @throws IOException + * if an I/O error occurs + * @throws UnsupportedSigningFormatException + * if a config is given and the wanted key format is not + * supported + */ + default void signObject(@NonNull Repository repository, + @NonNull GpgConfig config, @NonNull ObjectBuilder object, + @NonNull PersonIdent committer, String signingKey, + CredentialsProvider credentialsProvider) + throws CanceledException, IOException, + UnsupportedSigningFormatException { + try { + object.setGpgSignature(sign(repository, config, object.build(), + committer, signingKey, credentialsProvider)); + } catch (UnsupportedEncodingException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + + /** + * Signs arbitrary data. + * + * @param repository + * {@link Repository} the signature is created in + * @param config + * GPG settings from the git config + * @param data + * the data to sign + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param signingKey + * if non-{@code null} overrides the signing key from the config + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @return the signature for {@code data} + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + * @throws IOException + * if an I/O error occurs + * @throws UnsupportedSigningFormatException + * if a config is given and the wanted key format is not + * supported + */ + GpgSignature sign(@NonNull Repository repository, @NonNull GpgConfig config, + byte[] data, @NonNull PersonIdent committer, String signingKey, + CredentialsProvider credentialsProvider) throws CanceledException, + IOException, UnsupportedSigningFormatException; + + /** + * Indicates if a signing key is available for the specified committer + * and/or signing key. + * + * @param repository + * the current {@link Repository} + * @param config + * GPG settings from the git config + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param signingKey + * if non-{@code null} overrides the signing key from the config + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @return {@code true} if a signing key is available, {@code false} + * otherwise + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + */ + boolean canLocateSigningKey(@NonNull Repository repository, + @NonNull GpgConfig config, @NonNull PersonIdent committer, + String signingKey, CredentialsProvider credentialsProvider) + throws CanceledException; + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java new file mode 100644 index 0000000000..125d25e3b7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lib; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * A factory for {@link Signer}s. + * + * @since 7.0 + */ +public interface SignerFactory { + + /** + * Tells what kind of {@link Signer} this factory creates. + * + * @return the {@link GpgConfig.GpgFormat} of the signer + */ + @NonNull + GpgConfig.GpgFormat getType(); + + /** + * Creates a new instance of a {@link Signer} that can produce signatures of + * type {@link #getType()}. + * + * @return a new {@link Signer} + */ + @NonNull + Signer create(); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java new file mode 100644 index 0000000000..7771b07841 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.lib; + +import java.text.MessageFormat; +import java.util.EnumMap; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.JGitText; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages the available signers. + * + * @since 7.0 + */ +public final class Signers { + + private static final Logger LOG = LoggerFactory.getLogger(Signers.class); + + private static final Map<GpgConfig.GpgFormat, SignerFactory> SIGNER_FACTORIES = loadSigners(); + + private static final Map<GpgConfig.GpgFormat, Signer> SIGNERS = new ConcurrentHashMap<>(); + + private static Map<GpgConfig.GpgFormat, SignerFactory> loadSigners() { + Map<GpgConfig.GpgFormat, SignerFactory> result = new EnumMap<>( + GpgConfig.GpgFormat.class); + try { + for (SignerFactory factory : ServiceLoader + .load(SignerFactory.class)) { + GpgConfig.GpgFormat format = factory.getType(); + SignerFactory existing = result.get(format); + if (existing != null) { + LOG.warn("{}", //$NON-NLS-1$ + MessageFormat.format( + JGitText.get().signatureServiceConflict, + "SignerFactory", format, //$NON-NLS-1$ + existing.getClass().getCanonicalName(), + factory.getClass().getCanonicalName())); + } else { + result.put(format, factory); + } + } + } catch (ServiceConfigurationError e) { + LOG.error(e.getMessage(), e); + } + return result; + } + + private Signers() { + // No instantiation + } + + /** + * Retrieves a {@link Signer} that can produce signatures of the given type + * {@code format}. + * + * @param format + * {@link GpgConfig.GpgFormat} the signer must support + * @return a {@link Signer}, or {@code null} if none is available + */ + public static Signer get(@NonNull GpgConfig.GpgFormat format) { + return SIGNERS.computeIfAbsent(format, f -> { + SignerFactory factory = SIGNER_FACTORIES.get(format); + if (factory == null) { + return null; + } + return factory.create(); + }); + } + + /** + * Sets a specific signer to use for a specific signature type. + * + * @param format + * signature type to set the {@code signer} for + * @param signer + * the {@link Signer} to use for signatures of type + * {@code format}; if {@code null}, a default implementation, if + * available, may be used. + */ + public static void set(@NonNull GpgConfig.GpgFormat format, Signer signer) { + if (signer == null) { + SIGNERS.remove(format); + } else { + SIGNERS.put(format, signer); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java index 9ddfd59a18..a10eb0c583 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java @@ -57,7 +57,6 @@ public abstract class StoredConfig extends Config { */ public abstract void save() throws IOException; - /** {@inheritDoc} */ @Override public void clear() { super.clear(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java index 3ba33422c6..6fb8f326cc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java @@ -59,20 +59,17 @@ public class SymbolicRef implements Ref { this.updateIndex = updateIndex; } - /** {@inheritDoc} */ @Override @NonNull public String getName() { return name; } - /** {@inheritDoc} */ @Override public boolean isSymbolic() { return true; } - /** {@inheritDoc} */ @Override @NonNull public Ref getLeaf() { @@ -82,35 +79,30 @@ public class SymbolicRef implements Ref { return dst; } - /** {@inheritDoc} */ @Override @NonNull public Ref getTarget() { return target; } - /** {@inheritDoc} */ @Override @Nullable public ObjectId getObjectId() { return getLeaf().getObjectId(); } - /** {@inheritDoc} */ @Override @NonNull public Storage getStorage() { return Storage.LOOSE; } - /** {@inheritDoc} */ @Override @Nullable public ObjectId getPeeledObjectId() { return getLeaf().getPeeledObjectId(); } - /** {@inheritDoc} */ @Override public boolean isPeeled() { return getLeaf().isPeeled(); @@ -128,7 +120,6 @@ public class SymbolicRef implements Ref { return updateIndex; } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java index facb4a54be..ea73d95102 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java @@ -206,24 +206,6 @@ public class TagBuilder extends ObjectBuilder { return os.toByteArray(); } - /** - * Format this builder's state as an annotated tag object. - * - * @return this object in the canonical annotated tag format, suitable for - * storage in a repository, or {@code null} if the tag cannot be - * encoded - * @deprecated since 5.11; use {@link #build()} instead - */ - @Deprecated - public byte[] toByteArray() { - try { - return build(); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java index 85aa0b6639..98ea2f97a3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java @@ -45,7 +45,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor { this.write = true; } - /** {@inheritDoc} */ @Override protected void onUpdate(String taskName, int workCurr, Duration duration) { StringBuilder s = new StringBuilder(); @@ -53,7 +52,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor { send(s); } - /** {@inheritDoc} */ @Override protected void onEndTask(String taskName, int workCurr, Duration duration) { StringBuilder s = new StringBuilder(); @@ -73,7 +71,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor { appendDuration(s, duration); } - /** {@inheritDoc} */ @Override protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt, Duration duration) { @@ -82,7 +79,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor { send(s); } - /** {@inheritDoc} */ @Override protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt, Duration duration) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java index e553955560..05eacdcd63 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java @@ -55,7 +55,6 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor { this.process = new Semaphore(0); } - /** {@inheritDoc} */ @Override public void start(int totalTasks) { if (!isMainThread()) @@ -63,7 +62,6 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor { pm.start(totalTasks); } - /** {@inheritDoc} */ @Override public void beginTask(String title, int totalWork) { if (!isMainThread()) @@ -132,14 +130,12 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor { pm.update(cnt); } - /** {@inheritDoc} */ @Override public void update(int completed) { if (0 == pendingUpdates.getAndAdd(completed)) process.release(); } - /** {@inheritDoc} */ @Override public boolean isCancelled() { lock.lock(); @@ -150,7 +146,6 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor { } } - /** {@inheritDoc} */ @Override public void endTask() { if (!isMainThread()) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java index e1f8684830..c3c170a2c9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java @@ -332,7 +332,6 @@ public class TreeFormatter { } } - /** {@inheritDoc} */ @SuppressWarnings("nls") @Override public String toString() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java index c4eb8f10d5..3d4e0d1f3c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.util.FS; @@ -50,13 +51,40 @@ public interface TypedConfigGetter { * default value to return if no value was present. * @return true if any value or defaultValue is true, false for missing or * explicit false + * @deprecated use + * {@link #getBoolean(Config, String, String, String, Boolean)} + * instead */ + @Deprecated boolean getBoolean(Config config, String section, String subsection, String name, boolean defaultValue); /** + * Get a boolean value from a git {@link Config}. + * + * @param config + * to get the value from + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return true if any value or defaultValue is true, false for missing or + * explicit false + * @since 7.2 + */ + @Nullable + Boolean getBoolean(Config config, String section, String subsection, + String name, @Nullable Boolean defaultValue); + + /** * Parse an enumeration from a git {@link Config}. * + * @param <T> + * type of the enum * @param config * to get the value from * @param all @@ -72,8 +100,9 @@ public interface TypedConfigGetter { * default value to return if no value was present. * @return the selected enumeration value, or {@code defaultValue}. */ + @Nullable <T extends Enum<?>> T getEnum(Config config, T[] all, String section, - String subsection, String name, T defaultValue); + String subsection, String name, @Nullable T defaultValue); /** * Obtain an integer value from a git {@link Config}. @@ -89,11 +118,34 @@ public interface TypedConfigGetter { * @param defaultValue * default value to return if no value was present. * @return an integer value from the configuration, or defaultValue. + * @deprecated use {@link #getInt(Config, String, String, String, Integer)} + * instead */ + @Deprecated int getInt(Config config, String section, String subsection, String name, int defaultValue); /** + * Obtain an integer value from a git {@link Config}. + * + * @param config + * to get the value from + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return an integer value from the configuration, or defaultValue. + * @since 7.2 + */ + @Nullable + Integer getInt(Config config, String section, String subsection, + String name, @Nullable Integer defaultValue); + + /** * Obtain an integer value from a git {@link Config} which must be in given * range. * @@ -115,11 +167,43 @@ public interface TypedConfigGetter { * @return an integer value from the configuration, or defaultValue. * {@code #UNSET_INT} if unset. * @since 6.1 + * @deprecated use + * {@link #getIntInRange(Config, String, String, String, int, int, Integer)} + * instead */ + @Deprecated int getIntInRange(Config config, String section, String subsection, String name, int minValue, int maxValue, int defaultValue); /** + * Obtain an integer value from a git {@link Config} which must be in given + * range. + * + * @param config + * to get the value from + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param minValue + * minimal value + * @param maxValue + * maximum value + * @param defaultValue + * default value to return if no value was present. Use + * {@code #UNSET_INT} to set the default to unset. + * @return an integer value from the configuration, or defaultValue. + * {@code #UNSET_INT} if unset. + * @since 7.2 + */ + @Nullable + Integer getIntInRange(Config config, String section, String subsection, + String name, int minValue, int maxValue, + @Nullable Integer defaultValue); + + /** * Obtain a long value from a git {@link Config}. * * @param config @@ -133,11 +217,34 @@ public interface TypedConfigGetter { * @param defaultValue * default value to return if no value was present. * @return a long value from the configuration, or defaultValue. + * @deprecated use {@link #getLong(Config, String, String, String, Long)} + * instead */ + @Deprecated long getLong(Config config, String section, String subsection, String name, long defaultValue); /** + * Obtain a long value from a git {@link Config}. + * + * @param config + * to get the value from + * @param section + * section the key is grouped within. + * @param subsection + * subsection name, such a remote or branch name. + * @param name + * name of the key to get. + * @param defaultValue + * default value to return if no value was present. + * @return a long value from the configuration, or defaultValue. + * @since 7.2 + */ + @Nullable + Long getLong(Config config, String section, String subsection, String name, + @Nullable Long defaultValue); + + /** * Parse a numerical time unit, such as "1 minute", from a git * {@link Config}. * @@ -157,11 +264,41 @@ public interface TypedConfigGetter { * indication of the units. * @return the value, or {@code defaultValue} if not set, expressed in * {@code units}. + * @deprecated use + * {@link #getTimeUnit(Config, String, String, String, Long, TimeUnit)} + * instead */ + @Deprecated long getTimeUnit(Config config, String section, String subsection, String name, long defaultValue, TimeUnit wantUnit); /** + * Parse a numerical time unit, such as "1 minute", from a git + * {@link Config}. + * + * @param config + * to get the value from + * @param section + * section the key is in. + * @param subsection + * subsection the key is in, or null if not in a subsection. + * @param name + * the key name. + * @param defaultValue + * default value to return if no value was present. + * @param wantUnit + * the units of {@code defaultValue} and the return value, as + * well as the units to assume if the value does not contain an + * indication of the units. + * @return the value, or {@code defaultValue} if not set, expressed in + * {@code units}. + * @since 7.2 + */ + @Nullable + Long getTimeUnit(Config config, String section, String subsection, + String name, @Nullable Long defaultValue, TimeUnit wantUnit); + + /** * Parse a string value from a git {@link Config} and treat it as a file * path, replacing a ~/ prefix by the user's home directory. * <p> @@ -187,9 +324,10 @@ public interface TypedConfigGetter { * @return the {@link Path}, or {@code defaultValue} if not set * @since 5.10 */ + @Nullable default Path getPath(Config config, String section, String subsection, String name, @NonNull FS fs, File resolveAgainst, - Path defaultValue) { + @Nullable Path defaultValue) { String value = config.getString(section, subsection, name); if (value == null) { return defaultValue; |