summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
authorMatthias Sohn <matthias.sohn@sap.com>2021-09-09 14:41:30 +0200
committerMatthias Sohn <matthias.sohn@sap.com>2021-09-13 23:53:34 +0200
commita19494b735528b6eb678ac9dbc6a92b8db8560ab (patch)
tree29df569134d27876ad3b32a8f3f6daf4dffde5b5 /org.eclipse.jgit
parentb9653ccdad437c5449f8eaf0c4e9cfdd2afe4519 (diff)
parenta3a8de310847963bd8fadba33de17abd974ae710 (diff)
downloadjgit-a19494b735528b6eb678ac9dbc6a92b8db8560ab.tar.gz
jgit-a19494b735528b6eb678ac9dbc6a92b8db8560ab.zip
Merge branch 'master' into next
* master: (38 commits) Revert "DFS block cache: Refactor to enable parallel index loading" GitServlet: allow to override default error handlers Silence API error for new interface method ProtocolV2Hook#onObjectInfo transport: add object-info capability Ignore IllegalStateException if JVM is already shutting down Update orbit to R20210825222808 for 2021-09 Update spotbugs-maven-plugin to 4.3.0 Update ant to 1.10.11 also in pom.xml DFS block cache: add additional stats to DfsReaderIoStats Update Orbit to S20210817231813 [gpg] Better GPG home directory determination FS: cleanup use of final modifier Ensure FS#searchPath only selects executable files RevWalk: getMergedInto's result is wrong on the second call DFS block cache: Refactor to enable parallel index loading [test] Create keystore with the keytool of the running JDK [gpg] Update to Bouncy Castle 1.69 [test] Create keystore with the keytool of the running JDK [sshd] Minor code clean-up Support commit.template config property ... Change-Id: I9f99e9a513a23c0c0d252334e79c351512d7355e
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/.settings/.api_filters8
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java96
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java126
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java41
18 files changed, 498 insertions, 36 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 14c505de01..83a83817f4 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -8,6 +8,14 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/transport/ProtocolV2Hook.java" type="org.eclipse.jgit.transport.ProtocolV2Hook">
+ <filter id="404000815">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.transport.ProtocolV2Hook"/>
+ <message_argument value="onObjectInfo(ObjectInfoRequest)"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/transport/SshConstants.java" type="org.eclipse.jgit.transport.SshConstants">
<filter id="1142947843">
<message_arguments>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 33e7452d96..3acceab098 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -365,6 +365,7 @@ invalidBooleanValue=Invalid boolean value: {0}.{1}={2}
invalidChannel=Invalid channel {0}
invalidCommitParentNumber=Invalid commit parent number
invalidDepth=Invalid depth: {0}
+invalidEncoding=Invalid encoding from git config i18n.commitEncoding: {0}
invalidEncryption=Invalid encryption
invalidExpandWildcard=ExpandFromSource on a refspec that can have mismatched wildcards does not make sense.
invalidFilter=Invalid filter: {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index cf7bc1f263..3aa711455b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -173,7 +173,11 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
Repository repository = init();
FetchResult fetchResult = null;
Thread cleanupHook = new Thread(() -> cleanup());
- Runtime.getRuntime().addShutdownHook(cleanupHook);
+ try {
+ Runtime.getRuntime().addShutdownHook(cleanupHook);
+ } catch (IllegalStateException e) {
+ // ignore - the VM is already shutting down
+ }
try {
fetchResult = fetch(repository, u);
} catch (IOException ioe) {
@@ -197,7 +201,11 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
cleanup();
throw e;
} finally {
- Runtime.getRuntime().removeShutdownHook(cleanupHook);
+ try {
+ Runtime.getRuntime().removeShutdownHook(cleanupHook);
+ } catch (IllegalStateException e) {
+ // ignore - the VM is already shutting down
+ }
}
if (!noCheckout) {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 8622e0a1a8..76340dabb3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -393,6 +393,7 @@ public class JGitText extends TranslationBundle {
/***/ public String invalidChannel;
/***/ public String invalidCommitParentNumber;
/***/ public String invalidDepth;
+ /***/ public String invalidEncoding;
/***/ public String invalidEncryption;
/***/ public String invalidExpandWildcard;
/***/ public String invalidFilter;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index 96ca690c1c..9be3df3b17 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -25,6 +25,7 @@ import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.text.MessageFormat;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
@@ -162,10 +163,17 @@ public final class DfsPackFile extends BlockBasedFile {
try {
DfsStreamKey idxKey = desc.getStreamKey(INDEX);
+ AtomicBoolean cacheHit = new AtomicBoolean(true);
DfsBlockCache.Ref<PackIndex> idxref = cache.getOrLoadRef(
idxKey,
REF_POSITION,
- () -> loadPackIndex(ctx, idxKey));
+ () -> {
+ cacheHit.set(false);
+ return loadPackIndex(ctx, idxKey);
+ });
+ if (cacheHit.get()) {
+ ctx.stats.idxCacheHit++;
+ }
PackIndex idx = idxref.get();
if (index == null && idx != null) {
index = idx;
@@ -200,10 +208,17 @@ public final class DfsPackFile extends BlockBasedFile {
PackIndex idx = idx(ctx);
PackReverseIndex revidx = getReverseIdx(ctx);
DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX);
+ AtomicBoolean cacheHit = new AtomicBoolean(true);
DfsBlockCache.Ref<PackBitmapIndex> idxref = cache.getOrLoadRef(
bitmapKey,
REF_POSITION,
- () -> loadBitmapIndex(ctx, bitmapKey, idx, revidx));
+ () -> {
+ cacheHit.set(false);
+ return loadBitmapIndex(ctx, bitmapKey, idx, revidx);
+ });
+ if (cacheHit.get()) {
+ ctx.stats.bitmapCacheHit++;
+ }
PackBitmapIndex bmidx = idxref.get();
if (bitmapIndex == null && bmidx != null) {
bitmapIndex = bmidx;
@@ -225,10 +240,17 @@ public final class DfsPackFile extends BlockBasedFile {
PackIndex idx = idx(ctx);
DfsStreamKey revKey = new DfsStreamKey.ForReverseIndex(
desc.getStreamKey(INDEX));
+ AtomicBoolean cacheHit = new AtomicBoolean(true);
DfsBlockCache.Ref<PackReverseIndex> revref = cache.getOrLoadRef(
revKey,
REF_POSITION,
- () -> loadReverseIdx(revKey, idx));
+ () -> {
+ cacheHit.set(false);
+ return loadReverseIdx(ctx, revKey, idx);
+ });
+ if (cacheHit.get()) {
+ ctx.stats.ridxCacheHit++;
+ }
PackReverseIndex revidx = revref.get();
if (reverseIndex == null && revidx != null) {
reverseIndex = revidx;
@@ -1031,9 +1053,12 @@ public final class DfsPackFile extends BlockBasedFile {
}
private DfsBlockCache.Ref<PackReverseIndex> loadReverseIdx(
- DfsStreamKey revKey, PackIndex idx) {
+ DfsReader ctx, DfsStreamKey revKey, PackIndex idx) {
+ ctx.stats.readReverseIdx++;
+ long start = System.nanoTime();
PackReverseIndex revidx = new PackReverseIndex(idx);
reverseIndex = revidx;
+ ctx.stats.readReverseIdxMicros += elapsedMicros(start);
return new DfsBlockCache.Ref<>(
revKey,
REF_POSITION,
@@ -1064,8 +1089,8 @@ public final class DfsPackFile extends BlockBasedFile {
bmidx = PackBitmapIndex.read(in, idx, revidx);
} finally {
size = rc.position();
- ctx.stats.readIdxBytes += size;
- ctx.stats.readIdxMicros += elapsedMicros(start);
+ ctx.stats.readBitmapIdxBytes += size;
+ ctx.stats.readBitmapIdxMicros += elapsedMicros(start);
}
bitmapIndex = bmidx;
return new DfsBlockCache.Ref<>(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
index b7a71969b8..5c47425013 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
@@ -19,18 +19,39 @@ public class DfsReaderIoStats {
/** Number of times the reader explicitly called scanPacks. */
long scanPacks;
+ /** Total number of cache hits for pack indexes. */
+ long idxCacheHit;
+
+ /** Total number of cache hits for reverse indexes. */
+ long ridxCacheHit;
+
+ /** Total number of cache hits for bitmap indexes. */
+ long bitmapCacheHit;
+
/** Total number of complete pack indexes read into memory. */
long readIdx;
/** Total number of complete bitmap indexes read into memory. */
long readBitmap;
- /** Total number of bytes read from indexes. */
+ /** Total number of reverse indexes added into memory. */
+ long readReverseIdx;
+
+ /** Total number of bytes read from pack indexes. */
long readIdxBytes;
- /** Total microseconds spent reading pack or bitmap indexes. */
+ /** Total microseconds spent reading pack indexes. */
long readIdxMicros;
+ /** Total microseconds spent creating reverse indexes. */
+ long readReverseIdxMicros;
+
+ /** Total number of bytes read from bitmap indexes. */
+ long readBitmapIdxBytes;
+
+ /** Total microseconds spent reading bitmap indexes. */
+ long readBitmapIdxMicros;
+
/** Total number of block cache hits. */
long blockCacheHit;
@@ -75,6 +96,33 @@ public class DfsReaderIoStats {
}
/**
+ * Get total number of pack index cache hits.
+ *
+ * @return total number of pack index cache hits.
+ */
+ public long getPackIndexCacheHits() {
+ return stats.idxCacheHit;
+ }
+
+ /**
+ * Get total number of reverse index cache hits.
+ *
+ * @return total number of reverse index cache hits.
+ */
+ public long getReverseIndexCacheHits() {
+ return stats.ridxCacheHit;
+ }
+
+ /**
+ * Get total number of bitmap index cache hits.
+ *
+ * @return total number of bitmap index cache hits.
+ */
+ public long getBitmapIndexCacheHits() {
+ return stats.bitmapCacheHit;
+ }
+
+ /**
* Get total number of complete pack indexes read into memory.
*
* @return total number of complete pack indexes read into memory.
@@ -84,6 +132,15 @@ public class DfsReaderIoStats {
}
/**
+ * Get total number of times the reverse index was computed.
+ *
+ * @return total number of reverse index was computed.
+ */
+ public long getReadReverseIndexCount() {
+ return stats.readReverseIdx;
+ }
+
+ /**
* Get total number of complete bitmap indexes read into memory.
*
* @return total number of complete bitmap indexes read into memory.
@@ -93,24 +150,51 @@ public class DfsReaderIoStats {
}
/**
- * Get total number of bytes read from indexes.
+ * Get total number of bytes read from pack indexes.
*
- * @return total number of bytes read from indexes.
+ * @return total number of bytes read from pack indexes.
*/
public long getReadIndexBytes() {
return stats.readIdxBytes;
}
/**
- * Get total microseconds spent reading pack or bitmap indexes.
+ * Get total microseconds spent reading pack indexes.
*
- * @return total microseconds spent reading pack or bitmap indexes.
+ * @return total microseconds spent reading pack indexes.
*/
public long getReadIndexMicros() {
return stats.readIdxMicros;
}
/**
+ * Get total microseconds spent creating reverse indexes.
+ *
+ * @return total microseconds spent creating reverse indexes.
+ */
+ public long getReadReverseIndexMicros() {
+ return stats.readReverseIdxMicros;
+ }
+
+ /**
+ * Get total number of bytes read from bitmap indexes.
+ *
+ * @return total number of bytes read from bitmap indexes.
+ */
+ public long getReadBitmapIndexBytes() {
+ return stats.readBitmapIdxBytes;
+ }
+
+ /**
+ * Get total microseconds spent reading bitmap indexes.
+ *
+ * @return total microseconds spent reading bitmap indexes.
+ */
+ public long getReadBitmapIndexMicros() {
+ return stats.readBitmapIdxMicros;
+ }
+
+ /**
* Get total number of block cache hits.
*
* @return total number of block cache hits.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
new file mode 100644
index 0000000000..e4e7cd6e03
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020 Julian Ruppel <julian.ruppel@sap.com>
+ *
+ * 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.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.text.MessageFormat;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * The standard "commit" configuration parameters.
+ *
+ * @since 5.13
+ */
+public class CommitConfig {
+ /**
+ * Key for {@link Config#get(SectionParser)}.
+ */
+ public static final Config.SectionParser<CommitConfig> KEY = CommitConfig::new;
+
+ private final static Charset DEFAULT_COMMIT_MESSAGE_ENCODING = StandardCharsets.UTF_8;
+
+ private String i18nCommitEncoding;
+
+ private String commitTemplatePath;
+
+ private CommitConfig(Config rc) {
+ commitTemplatePath = rc.getString(ConfigConstants.CONFIG_COMMIT_SECTION,
+ null, ConfigConstants.CONFIG_KEY_COMMIT_TEMPLATE);
+ i18nCommitEncoding = rc.getString(ConfigConstants.CONFIG_SECTION_I18N,
+ null, ConfigConstants.CONFIG_KEY_COMMIT_ENCODING);
+ }
+
+ /**
+ * Get the path to the commit template as defined in the git
+ * {@code commit.template} property.
+ *
+ * @return the path to commit template or {@code null} if not present.
+ */
+ @Nullable
+ public String getCommitTemplatePath() {
+ return commitTemplatePath;
+ }
+
+ /**
+ * Get the encoding of the commit as defined in the git
+ * {@code i18n.commitEncoding} property.
+ *
+ * @return the encoding or {@code null} if not present.
+ */
+ @Nullable
+ public String getCommitEncoding() {
+ return i18nCommitEncoding;
+ }
+
+ /**
+ * Get the content to the commit template as defined in
+ * {@code commit.template}. If no {@code i18n.commitEncoding} is specified,
+ * UTF-8 fallback is used.
+ *
+ * @return content of the commit template or {@code null} if not present.
+ * @throws IOException
+ * if the template file can not be read
+ * @throws FileNotFoundException
+ * if the template file does not exists
+ * @throws ConfigInvalidException
+ * if a {@code commitEncoding} is specified and is invalid
+ */
+ @Nullable
+ public String getCommitTemplateContent()
+ throws FileNotFoundException, IOException, ConfigInvalidException {
+
+ if (commitTemplatePath == null) {
+ return null;
+ }
+
+ File commitTemplateFile;
+ if (commitTemplatePath.startsWith("~/")) { //$NON-NLS-1$
+ commitTemplateFile = FS.DETECTED.resolve(FS.DETECTED.userHome(),
+ commitTemplatePath.substring(2));
+ } else {
+ commitTemplateFile = FS.DETECTED.resolve(null, commitTemplatePath);
+ }
+
+ Charset commitMessageEncoding = getEncoding();
+ return RawParseUtils.decode(commitMessageEncoding,
+ IO.readFully(commitTemplateFile));
+
+ }
+
+ private Charset getEncoding() throws ConfigInvalidException {
+ Charset commitMessageEncoding = DEFAULT_COMMIT_MESSAGE_ENCODING;
+
+ if (i18nCommitEncoding == null) {
+ return null;
+ }
+
+ try {
+ commitMessageEncoding = Charset.forName(i18nCommitEncoding);
+ } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+ throw new ConfigInvalidException(MessageFormat.format(
+ JGitText.get().invalidEncoding, i18nCommitEncoding), e);
+ }
+
+ return commitMessageEncoding;
+ }
+} \ No newline at end of file
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 b6f4798dce..24eebc6a19 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -125,6 +125,13 @@ public final class ConfigConstants {
public static final String CONFIG_COMMIT_SECTION = "commit";
/**
+ * The "template" key
+ *
+ * @since 5.13
+ */
+ public static final String CONFIG_KEY_COMMIT_TEMPLATE = "template";
+
+ /**
* The "tag" section
*
* @since 5.11
@@ -518,6 +525,13 @@ public final class ConfigConstants {
public static final String CONFIG_SECTION_I18N = "i18n";
/**
+ * The "commitEncoding" key
+ *
+ * @since 5.13
+ */
+ public static final String CONFIG_KEY_COMMIT_ENCODING = "commitEncoding";
+
+ /**
* The "logOutputEncoding" key
*
* @since 5.2
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index 5d5ba12baa..d5b3643af9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -527,10 +527,12 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
private List<Ref> getMergedInto(RevCommit needle, Collection<Ref> haystacks,
Enum returnStrategy, ProgressMonitor monitor) throws IOException {
List<Ref> result = new ArrayList<>();
+ List<RevCommit> uninteresting = new ArrayList<>();
RevFilter oldRF = filter;
TreeFilter oldTF = treeFilter;
try {
finishDelayedFreeFlags();
+ reset(~freeFlags & APP_FLAGS);
filter = RevFilter.ALL;
treeFilter = TreeFilter.ALL;
for (Ref r: haystacks) {
@@ -559,13 +561,14 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
}
if(!commitFound){
markUninteresting(c);
+ uninteresting.add(c);
if (returnStrategy == GetMergedIntoStrategy.RETURN_ON_FIRST_NOT_FOUND) {
return result;
}
}
}
} finally {
- reset(~freeFlags & APP_FLAGS);
+ roots.addAll(uninteresting);
filter = oldRF;
treeFilter = oldTF;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index c5e52bef98..aaa9308ac3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -255,6 +255,13 @@ public final class GitProtocolConstants {
public static final String COMMAND_FETCH = "fetch"; //$NON-NLS-1$
/**
+ * The server supports the object-info capability.
+ *
+ * @since 5.13
+ */
+ public static final String COMMAND_OBJECT_INFO = "object-info"; //$NON-NLS-1$
+
+ /**
* HTTP header to set by clients to request a specific git protocol version
* in the HTTP transport.
*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java
new file mode 100644
index 0000000000..86a2716675
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021, Google LLC. 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.transport;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * object-info request.
+ *
+ * <p>
+ * This is the parsed request for an object-info call, used as input to
+ * {@link ProtocolV2Hook}.
+ *
+ * @see <a href=
+ * "https://www.kernel.org/pub/software/scm/git/docs/technical/protocol-v2.html#_object_info">object-info
+ * documentation</a>
+ *
+ * @since 5.13
+ */
+public final class ObjectInfoRequest {
+ private final List<ObjectId> objectIDs;
+
+ private ObjectInfoRequest(List<ObjectId> objectIDs) {
+ this.objectIDs = objectIDs;
+ }
+
+ /** @return object IDs that the client requested. */
+ public List<ObjectId> getObjectIDs() {
+ return this.objectIDs;
+ }
+
+ /** @return A builder of {@link ObjectInfoRequest}. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** A builder for {@link ObjectInfoRequest}. */
+ public static final class Builder {
+ private List<ObjectId> objectIDs = Collections.emptyList();
+
+ private Builder() {
+ }
+
+ /**
+ * @param value
+ * @return the Builder
+ */
+ public Builder setObjectIDs(List<ObjectId> value) {
+ objectIDs = value;
+ return this;
+ }
+
+ /** @return ObjectInfoRequest */
+ public ObjectInfoRequest build() {
+ return new ObjectInfoRequest(
+ Collections.unmodifiableList(objectIDs));
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
index bfa7490665..d7626df3fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
@@ -55,4 +55,16 @@ public interface ProtocolV2Hook {
throws ServiceMayNotContinueException {
// Do nothing by default
}
+
+ /**
+ * @param req
+ * the object-info request
+ * @throws ServiceMayNotContinueException
+ * abort; the message will be sent to the user
+ * @since 5.13
+ */
+ default void onObjectInfo(ObjectInfoRequest req)
+ throws ServiceMayNotContinueException {
+ // Do nothing by default
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java
index 4cf8db6f5e..7b3a4cea06 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java
@@ -71,6 +71,14 @@ public class ProtocolV2HookChain implements ProtocolV2Hook {
}
}
+ @Override
+ public void onObjectInfo(ObjectInfoRequest req)
+ throws ServiceMayNotContinueException {
+ for (ProtocolV2Hook hook : hooks) {
+ hook.onObjectInfo(req);
+ }
+ }
+
private ProtocolV2HookChain(List<? extends ProtocolV2Hook> hooks) {
this.hooks = Collections.unmodifiableList(hooks);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
index 92f0133f5a..6cec4b9a3f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ObjectId;
@@ -248,4 +249,38 @@ final class ProtocolV2Parser {
return builder.setRefPrefixes(prefixes).build();
}
+ ObjectInfoRequest parseObjectInfoRequest(PacketLineIn pckIn)
+ throws PackProtocolException, IOException {
+ ObjectInfoRequest.Builder builder = ObjectInfoRequest.builder();
+ List<ObjectId> objectIDs = new ArrayList<>();
+
+ String line = pckIn.readString();
+
+ if (PacketLineIn.isEnd(line)) {
+ return builder.build();
+ }
+
+ if (!line.equals("size")) { //$NON-NLS-1$
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().unexpectedPacketLine, line));
+ }
+
+ for (String line2 : pckIn.readStrings()) {
+ if (!line2.startsWith("oid ")) { //$NON-NLS-1$
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().unexpectedPacketLine, line2));
+ }
+
+ String oidStr = line2.substring("oid ".length()); //$NON-NLS-1$
+
+ try {
+ objectIDs.add(ObjectId.fromString(oidStr));
+ } catch (InvalidObjectIdException e) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().invalidObject, oidStr), e);
+ }
+ }
+
+ return builder.setObjectIDs(objectIDs).build();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index 79f60c3202..58f8895e00 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -29,6 +29,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
@@ -37,6 +38,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.InvalidObjectIdException;
@@ -441,7 +444,7 @@ public class ReceivePack {
*/
public void setAdvertisedRefs(Map<String, Ref> allRefs,
Set<ObjectId> additionalHaves) {
- refs = allRefs != null ? allRefs : db.getAllRefs();
+ refs = allRefs != null ? allRefs : getAllRefs();
refs = refFilter.filter(refs);
advertisedHaves.clear();
@@ -1296,6 +1299,21 @@ public class ReceivePack {
}
/**
+ * Extract the full list of refs from the ref-db.
+ *
+ * @return Map of all refname/ref
+ */
+ private Map<String, Ref> getAllRefs() {
+ try {
+ return db.getRefDatabase().getRefs().stream()
+ .collect(Collectors.toMap(Ref::getName,
+ Function.identity()));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ /**
* Receive a list of commands from the input.
*
* @throws java.io.IOException
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index da97f1e580..02be434887 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -123,6 +123,7 @@ public class TransferConfig {
private final boolean advertiseSidebandAll;
private final boolean advertiseWaitForDone;
+ private final boolean advertiseObjectInfo;
final @Nullable ProtocolVersion protocolVersion;
final String[] hideRefs;
@@ -211,6 +212,8 @@ public class TransferConfig {
"advertisesidebandall", false);
advertiseWaitForDone = rc.getBoolean("uploadpack",
"advertisewaitfordone", false);
+ advertiseObjectInfo = rc.getBoolean("uploadpack",
+ "advertiseobjectinfo", false);
}
/**
@@ -318,6 +321,14 @@ public class TransferConfig {
}
/**
+ * @return true to advertise object-info to all clients
+ * @since 5.13
+ */
+ public boolean isAdvertiseObjectInfo() {
+ return advertiseObjectInfo;
+ }
+
+ /**
* Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured
* hidden refs.
*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 37a1c1e589..07b348d0e7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -17,6 +17,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SERVER_OPTION;
import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_FETCH;
import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_OBJECT_INFO;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
@@ -1269,6 +1270,32 @@ public class UploadPack {
}
}
+ private void objectInfo(PacketLineOut pckOut) throws IOException {
+ ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
+ ObjectInfoRequest req = parser.parseObjectInfoRequest(pckIn);
+
+ protocolV2Hook.onObjectInfo(req);
+
+ ObjectReader or = getRepository().newObjectReader();
+
+ // Size is the only attribute currently supported.
+ pckOut.writeString("size"); //$NON-NLS-1$
+
+ for (ObjectId oid : req.getObjectIDs()) {
+ long size;
+ try {
+ size = or.getObjectSize(oid, ObjectReader.OBJ_ANY);
+ } catch (MissingObjectException e) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().missingObject, oid.name()), e);
+ }
+
+ pckOut.writeString(oid.getName() + " " + size); //$NON-NLS-1$
+ }
+
+ pckOut.end();
+ }
+
/*
* Returns true if this is the last command and we should tear down the
* connection.
@@ -1295,6 +1322,10 @@ public class UploadPack {
fetchV2(pckOut);
return false;
}
+ if (command.equals("command=" + COMMAND_OBJECT_INFO)) { //$NON-NLS-1$
+ objectInfo(pckOut);
+ return false;
+ }
throw new PackProtocolException(MessageFormat
.format(JGitText.get().unknownTransportCommand, command));
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 0946f645fb..31a05933c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -524,7 +524,7 @@ public abstract class FS {
}
private static String read(Path p) throws IOException {
- final byte[] body = IO.readFully(p.toFile());
+ byte[] body = IO.readFully(p.toFile());
return new String(body, 0, body.length, UTF_8);
}
@@ -766,7 +766,7 @@ public abstract class FS {
}
private static String getConfigKey(FileStore s) {
- final String storeKey;
+ String storeKey;
if (SystemReader.getInstance().isWindows()) {
Object attribute = null;
try {
@@ -1164,7 +1164,7 @@ public abstract class FS {
* platform does not require path name translation.
*/
public File resolve(File dir, String name) {
- final File abspn = new File(name);
+ File abspn = new File(name);
if (abspn.isAbsolute())
return abspn;
return new File(dir, name);
@@ -1257,7 +1257,7 @@ public abstract class FS {
}
private File defaultUserHomeImpl() {
- final String home = AccessController.doPrivileged(
+ String home = AccessController.doPrivileged(
(PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
);
if (home == null || home.length() == 0)
@@ -1267,7 +1267,8 @@ public abstract class FS {
/**
* Searches the given path to see if it contains one of the given files.
- * Returns the first it finds. Returns null if not found or if path is null.
+ * Returns the first it finds which is executable. Returns null if not found
+ * or if path is null.
*
* @param path
* List of paths to search separated by File.pathSeparator
@@ -1277,14 +1278,15 @@ public abstract class FS {
* @since 3.0
*/
protected static File searchPath(String path, String... lookFor) {
- if (path == null)
+ if (path == null) {
return null;
+ }
for (String p : path.split(File.pathSeparator)) {
for (String command : lookFor) {
- final File file = new File(p, command);
+ File file = new File(p, command);
try {
- if (file.isFile()) {
+ if (file.isFile() && file.canExecute()) {
return file.getAbsoluteFile();
}
} catch (SecurityException e) {
@@ -1339,7 +1341,7 @@ public abstract class FS {
protected static String readPipe(File dir, String[] command,
String encoding, Map<String, String> env)
throws CommandFailedException {
- final boolean debug = LOG.isDebugEnabled();
+ boolean debug = LOG.isDebugEnabled();
try {
if (debug) {
LOG.debug("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
@@ -1806,11 +1808,11 @@ public abstract class FS {
* @since 5.0
*/
public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
- final File[] all = directory.listFiles();
+ File[] all = directory.listFiles();
if (all == null) {
return NO_ENTRIES;
}
- final Entry[] result = new Entry[all.length];
+ Entry[] result = new Entry[all.length];
for (int i = 0; i < result.length; i++) {
result[i] = new FileEntry(all[i], this, fileModeStrategy);
}
@@ -1840,10 +1842,9 @@ public abstract class FS {
* @since 4.0
*/
public ProcessResult runHookIfPresent(Repository repository,
- final String hookName,
- String[] args) throws JGitInternalException {
- return runHookIfPresent(repository, hookName, args, System.out, System.err,
- null);
+ String hookName, String[] args) throws JGitInternalException {
+ return runHookIfPresent(repository, hookName, args, System.out,
+ System.err, null);
}
/**
@@ -1875,9 +1876,9 @@ public abstract class FS {
* @since 5.11
*/
public ProcessResult runHookIfPresent(Repository repository,
- final String hookName,
- String[] args, OutputStream outRedirect, OutputStream errRedirect,
- String stdinArgs) throws JGitInternalException {
+ String hookName, String[] args, OutputStream outRedirect,
+ OutputStream errRedirect, String stdinArgs)
+ throws JGitInternalException {
return new ProcessResult(Status.NOT_SUPPORTED);
}
@@ -1911,7 +1912,7 @@ public abstract class FS {
* @since 5.11
*/
protected ProcessResult internalRunHookIfPresent(Repository repository,
- final String hookName, String[] args, OutputStream outRedirect,
+ String hookName, String[] args, OutputStream outRedirect,
OutputStream errRedirect, String stdinArgs)
throws JGitInternalException {
File hookFile = findHook(repository, hookName);
@@ -2107,7 +2108,7 @@ public abstract class FS {
OutputStream outRedirect, OutputStream errRedirect,
InputStream inRedirect) throws IOException,
InterruptedException {
- final ExecutorService executor = Executors.newFixedThreadPool(2);
+ ExecutorService executor = Executors.newFixedThreadPool(2);
Process process = null;
// We'll record the first I/O exception that occurs, but keep on trying
// to dispose of our open streams and file handles