aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/.settings/.api_filters139
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF109
-rw-r--r--org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF4
-rw-r--r--org.eclipse.jgit/pom.xml2
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java162
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java212
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java80
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java319
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java104
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java309
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java221
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java117
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java143
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java73
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java114
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java90
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java76
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java71
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java51
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java43
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java124
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java77
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java96
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java150
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java180
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java48
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java95
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BooleanTriState.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java99
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java104
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java204
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java81
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java122
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java205
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java45
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java8
76 files changed, 4005 insertions, 835 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 159736e7b8..24998cc8fe 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -39,6 +39,28 @@
<message_argument value="SHA1_IMPLEMENTATION"/>
</message_arguments>
</filter>
+ <filter id="1142947843">
+ <message_arguments>
+ <message_argument value="6.1.1"/>
+ <message_argument value="CONFIG_KEY_TRUST_PACKED_REFS_STAT"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/lib/CoreConfig.java" type="org.eclipse.jgit.lib.CoreConfig$TrustPackedRefsStat">
+ <filter id="1142947843">
+ <message_arguments>
+ <message_argument value="6.1.1"/>
+ <message_argument value="TrustPackedRefsStat"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/lib/ObjectDatabase.java" type="org.eclipse.jgit.lib.ObjectDatabase">
+ <filter id="336695337">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.ObjectDatabase"/>
+ <message_argument value="getApproximateObjectCount()"/>
+ </message_arguments>
+ </filter>
</resource>
<resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
<filter id="1142947843">
@@ -48,7 +70,47 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/lib/TypedConfigGetter.java" type="org.eclipse.jgit.lib.TypedConfigGetter">
+ <filter id="403767336">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+ <message_argument value="UNSET_INT"/>
+ </message_arguments>
+ </filter>
+ <filter id="403804204">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+ <message_argument value="getIntInRange(Config, String, String, String, int, int, int)"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
+ <filter id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+ <message_argument value="addCheckoutMetadata(String, Attributes)"/>
+ </message_arguments>
+ </filter>
+ <filter id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+ <message_argument value="addToCheckout(String, DirCacheEntry, Attributes)"/>
+ </message_arguments>
+ </filter>
+ <filter id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+ <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean, Attributes)"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
+ <filter id="336658481">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
+ <message_argument value="DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+ </message_arguments>
+ </filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.13.2"/>
@@ -76,6 +138,22 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/transport/BasePackPushConnection.java" type="org.eclipse.jgit.transport.BasePackPushConnection">
+ <filter id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.transport.BasePackPushConnection"/>
+ <message_argument value="noRepository()"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/transport/PushConfig.java" type="org.eclipse.jgit.transport.PushConfig">
+ <filter id="338722907">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.transport.PushConfig"/>
+ <message_argument value="PushConfig()"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/util/HttpSupport.java" type="org.eclipse.jgit.util.HttpSupport">
<filter id="1142947843">
<message_arguments>
@@ -84,6 +162,67 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/util/sha1/SHA1.java" type="org.eclipse.jgit.util.sha1.SHA1">
+ <filter id="337764418">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="digest()"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="digest(MutableObjectId)"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="hasCollision()"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="reset()"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="setDetectCollision(boolean)"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="toObjectId()"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="update(byte)"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="update(byte[])"/>
+ </message_arguments>
+ </filter>
+ <filter id="421650549">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.util.sha1.SHA1"/>
+ <message_argument value="update(byte[], int, int)"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/util/sha1/SHA1.java" type="org.eclipse.jgit.util.sha1.SHA1$Sha1Implementation">
<filter id="1142947843">
<message_arguments>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 87b58e1fc5..808d5d37b3 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -3,12 +3,12 @@ Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Automatic-Module-Name: org.eclipse.jgit
Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 6.0.1.qualifier
+Bundle-Version: 6.1.1.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %Bundle-Vendor
Eclipse-ExtensibleAPI: true
-Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
- org.eclipse.jgit.api;version="6.0.1";
+Export-Package: org.eclipse.jgit.annotations;version="6.1.1",
+ org.eclipse.jgit.api;version="6.1.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.notes,
org.eclipse.jgit.dircache,
@@ -23,18 +23,18 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
org.eclipse.jgit.revwalk.filter,
org.eclipse.jgit.blame,
org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="6.0.1";
+ org.eclipse.jgit.api.errors;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="6.0.1";
+ org.eclipse.jgit.attributes;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.blame;version="6.0.1";
+ org.eclipse.jgit.blame;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="6.0.1";
+ org.eclipse.jgit.diff;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.attributes,
org.eclipse.jgit.revwalk,
@@ -42,44 +42,48 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="6.0.1";
+ org.eclipse.jgit.dircache;version="6.1.1";
uses:="org.eclipse.jgit.events,
org.eclipse.jgit.lib,
org.eclipse.jgit.attributes,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.errors;version="6.0.1";
+ org.eclipse.jgit.errors;version="6.1.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.internal.storage.pack",
- org.eclipse.jgit.events;version="6.0.1";
+ org.eclipse.jgit.events;version="6.1.1";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="6.0.1",
- org.eclipse.jgit.gitrepo;version="6.0.1";
+ org.eclipse.jgit.fnmatch;version="6.1.1",
+ org.eclipse.jgit.gitrepo;version="6.1.1";
uses:="org.xml.sax.helpers,
org.eclipse.jgit.api,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="6.0.1";x-internal:=true,
- org.eclipse.jgit.hooks;version="6.0.1";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="6.0.1",
- org.eclipse.jgit.ignore.internal;version="6.0.1";
+ org.eclipse.jgit.gitrepo.internal;version="6.1.1";x-internal:=true,
+ org.eclipse.jgit.hooks;version="6.1.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="6.1.1",
+ org.eclipse.jgit.ignore.internal;version="6.1.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="6.0.1";
+ org.eclipse.jgit.internal;version="6.1.1";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="6.0.1";
+ org.eclipse.jgit.internal.diffmergetool;version="6.1.1";
+ x-friends:="org.eclipse.jgit.test,
+ org.eclipse.jgit.pgm.test,
+ org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.fsck;version="6.1.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.revwalk;version="6.0.1";
+ org.eclipse.jgit.internal.revwalk;version="6.1.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.dfs;version="6.0.1";
+ org.eclipse.jgit.internal.storage.dfs;version="6.1.1";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.http.server,
org.eclipse.jgit.http.test,
org.eclipse.jgit.lfs.test",
- org.eclipse.jgit.internal.storage.file;version="6.0.1";
+ org.eclipse.jgit.internal.storage.file;version="6.1.1";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.junit.http,
@@ -88,32 +92,32 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
org.eclipse.jgit.pgm,
org.eclipse.jgit.pgm.test,
org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.internal.storage.io;version="6.0.1";
+ org.eclipse.jgit.internal.storage.io;version="6.1.1";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="6.0.1";
+ org.eclipse.jgit.internal.storage.pack;version="6.1.1";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="6.0.1";
+ org.eclipse.jgit.internal.storage.reftable;version="6.1.1";
x-friends:="org.eclipse.jgit.http.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.submodule;version="6.0.1";x-internal:=true,
- org.eclipse.jgit.internal.transport.connectivity;version="6.0.1";
+ org.eclipse.jgit.internal.submodule;version="6.1.1";x-internal:=true,
+ org.eclipse.jgit.internal.transport.connectivity;version="6.1.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.http;version="6.0.1";
+ org.eclipse.jgit.internal.transport.http;version="6.1.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.parser;version="6.0.1";
+ org.eclipse.jgit.internal.transport.parser;version="6.1.1";
x-friends:="org.eclipse.jgit.http.server,
org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="6.0.1";
+ org.eclipse.jgit.internal.transport.ssh;version="6.1.1";
x-friends:="org.eclipse.jgit.ssh.apache,
org.eclipse.jgit.ssh.jsch,
org.eclipse.jgit.test",
- org.eclipse.jgit.lib;version="6.0.1";
+ org.eclipse.jgit.lib;version="6.1.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.util.sha1,
org.eclipse.jgit.dircache,
@@ -127,10 +131,11 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
org.eclipse.jgit.util,
org.eclipse.jgit.submodule,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.lib.internal;version="6.0.1";
- x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.logging;version="6.0.1",
- org.eclipse.jgit.merge;version="6.0.1";
+ org.eclipse.jgit.lib.internal;version="6.1.1";
+ x-friends:="org.eclipse.jgit.test,
+ org.eclipse.jgit.pgm",
+ org.eclipse.jgit.logging;version="6.1.1",
+ org.eclipse.jgit.merge;version="6.1.1";
uses:="org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
@@ -139,40 +144,40 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
org.eclipse.jgit.util,
org.eclipse.jgit.api,
org.eclipse.jgit.attributes",
- org.eclipse.jgit.nls;version="6.0.1",
- org.eclipse.jgit.notes;version="6.0.1";
+ org.eclipse.jgit.nls;version="6.1.1",
+ org.eclipse.jgit.notes;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="6.0.1";
+ org.eclipse.jgit.patch;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="6.0.1";
+ org.eclipse.jgit.revplot;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="6.0.1";
+ org.eclipse.jgit.revwalk;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.diff,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.revwalk.filter,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.revwalk.filter;version="6.0.1";
+ org.eclipse.jgit.revwalk.filter;version="6.1.1";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="6.0.1";
+ org.eclipse.jgit.storage.file;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="6.0.1";
+ org.eclipse.jgit.storage.pack;version="6.1.1";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="6.0.1";
+ org.eclipse.jgit.submodule;version="6.1.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.diff,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.transport;version="6.0.1";
+ org.eclipse.jgit.transport;version="6.1.1";
uses:="javax.crypto,
org.eclipse.jgit.util.io,
org.eclipse.jgit.lib,
@@ -185,21 +190,21 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.storage.pack,
org.eclipse.jgit.errors",
- org.eclipse.jgit.transport.http;version="6.0.1";
+ org.eclipse.jgit.transport.http;version="6.1.1";
uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="6.0.1";
+ org.eclipse.jgit.transport.resolver;version="6.1.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.lib",
- org.eclipse.jgit.treewalk;version="6.0.1";
+ org.eclipse.jgit.treewalk;version="6.1.1";
uses:="org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.attributes,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util",
- org.eclipse.jgit.treewalk.filter;version="6.0.1";
+ org.eclipse.jgit.treewalk.filter;version="6.1.1";
uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="6.0.1";
+ org.eclipse.jgit.util;version="6.1.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.hooks,
org.eclipse.jgit.revwalk,
@@ -212,12 +217,12 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.1",
org.eclipse.jgit.treewalk,
javax.net.ssl,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.util.io;version="6.0.1";
+ org.eclipse.jgit.util.io;version="6.1.1";
uses:="org.eclipse.jgit.attributes,
org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util.sha1;version="6.0.1",
- org.eclipse.jgit.util.time;version="6.0.1"
+ org.eclipse.jgit.util.sha1;version="6.1.1",
+ org.eclipse.jgit.util.time;version="6.1.1"
Bundle-RequiredExecutionEnvironment: JavaSE-11
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
javax.crypto,
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 302c4548a3..33e6156773 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2
Bundle-Name: org.eclipse.jgit - Sources
Bundle-SymbolicName: org.eclipse.jgit.source
Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.0.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="6.0.1.qualifier";roots="."
+Bundle-Version: 6.1.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="6.1.1.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index e78227250f..f0e92b3894 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>6.0.1-SNAPSHOT</version>
+ <version>6.1.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit</artifactId>
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 c887c6dfbf..5534a0a44c 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -359,6 +359,8 @@ initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {
inMemoryBufferLimitExceeded=In-memory buffer limit exceeded
inputDidntMatchLength=Input did not match supplied length. {0} bytes are missing.
inputStreamMustSupportMark=InputStream must support mark()
+integerValueNotInRange=Integer value {0}.{1} = {2} not in range {3}..{4}
+integerValueNotInRangeSubSection=Integer value {0}.{1}.{2} = {3} not in range {4}..{5}
integerValueOutOfRange=Integer value {0}.{1} out of range
internalRevisionError=internal revision error
internalServerError=internal server error
@@ -370,6 +372,7 @@ invalidAwsApiSignatureVersion=Invalid aws.api.signature.version: {0}
invalidBooleanValue=Invalid boolean value: {0}.{1}={2}
invalidChannel=Invalid channel {0}
invalidCommitParentNumber=Invalid commit parent number
+invalidCoreAbbrev=Invalid value {0} of option core.abbrev
invalidDepth=Invalid depth: {0}
invalidEncoding=Invalid encoding from git config i18n.commitEncoding: {0}
invalidEncryption=Invalid encryption
@@ -571,6 +574,11 @@ pushCertificateInvalidField=Push certificate has missing or invalid value for {0
pushCertificateInvalidFieldValue=Push certificate has missing or invalid value for {0}: {1}
pushCertificateInvalidHeader=Push certificate has invalid header format
pushCertificateInvalidSignature=Push certificate has invalid signature format
+pushDefaultNothing=No refspec given and push.default=nothing; no upstream branch can be determined
+pushDefaultNoUpstream=No upstream branch found for local branch ''{0}''
+pushDefaultSimple=push.default=simple requires local branch name ''{0}'' to be equal to upstream tracked branch name ''{1}''
+pushDefaultTriangularUpstream=push.default=upstream cannot be used when the push remote ''{0}'' is different from the fetch remote ''{1}''
+pushDefaultUnknown=Unknown push.default={0}; cannot push
pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport
pushNotPermitted=push not permitted
pushOptionsNotSupported=Push options not supported; received {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 7922f9e729..f88179ac1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -9,6 +9,8 @@
*/
package org.eclipse.jgit.api;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
import java.io.IOException;
import java.text.MessageFormat;
import java.util.LinkedList;
@@ -124,7 +126,7 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
final RevCommit srcParent = getParentCommit(srcCommit, revWalk);
String ourName = calculateOurName(headRef);
- String cherryPickName = srcCommit.getId().abbreviate(7).name()
+ String cherryPickName = srcCommit.getId().abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name()
+ " " + srcCommit.getShortMessage(); //$NON-NLS-1$
Merger merger = strategy.newMerger(repo);
@@ -183,7 +185,7 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
if (unmergedPaths != null) {
message = new MergeMessageFormatter()
.formatWithConflicts(srcCommit.getFullMessage(),
- unmergedPaths);
+ unmergedPaths, '#');
} else {
message = srcCommit.getFullMessage();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 37f1d482aa..7a591aa3b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -19,6 +19,7 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
@@ -46,6 +47,8 @@ import org.eclipse.jgit.hooks.PostCommitHook;
import org.eclipse.jgit.hooks.PreCommitHook;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.CommitConfig;
+import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.GpgConfig;
@@ -133,6 +136,12 @@ public class CommitCommand extends GitCommand<RevCommit> {
private CredentialsProvider credentialsProvider;
+ private @NonNull CleanupMode cleanupMode = CleanupMode.VERBATIM;
+
+ private boolean cleanDefaultIsStrip = true;
+
+ private Character commentChar;
+
/**
* Constructor for CommitCommand
*
@@ -200,7 +209,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
throw new WrongRepositoryStateException(
JGitText.get().commitAmendOnInitialNotPossible);
- if (headId != null)
+ if (headId != null) {
if (amend) {
RevCommit previousCommit = rw.parseCommit(headId);
for (RevCommit p : previousCommit.getParents())
@@ -210,7 +219,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
} else {
parents.add(0, headId);
}
-
+ }
if (!noVerify) {
message = Hooks
.commitMsg(repo,
@@ -219,6 +228,19 @@ public class CommitCommand extends GitCommand<RevCommit> {
.setCommitMessage(message).call();
}
+ CommitConfig config = null;
+ if (CleanupMode.DEFAULT.equals(cleanupMode)) {
+ config = repo.getConfig().get(CommitConfig.KEY);
+ cleanupMode = config.resolve(cleanupMode, cleanDefaultIsStrip);
+ }
+ char comments;
+ if (commentChar == null) {
+ comments = '#'; // TODO use git config core.commentChar
+ } else {
+ comments = commentChar.charValue();
+ }
+ message = CommitConfig.cleanText(message, cleanupMode, comments);
+
RevCommit revCommit;
DirCache index = repo.lockDirCache();
try (ObjectInserter odi = repo.newObjectInserter()) {
@@ -658,6 +680,57 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
/**
+ * Sets the {@link CleanupMode} to apply to the commit message. If not
+ * called, {@link CommitCommand} applies {@link CleanupMode#VERBATIM}.
+ *
+ * @param mode
+ * {@link CleanupMode} to set
+ * @return {@code this}
+ * @since 6.1
+ */
+ public CommitCommand setCleanupMode(@NonNull CleanupMode mode) {
+ checkCallable();
+ this.cleanupMode = mode;
+ return this;
+ }
+
+ /**
+ * Sets the default clean mode if {@link #setCleanupMode(CleanupMode)
+ * setCleanupMode(CleanupMode.DEFAULT)} is set and git config
+ * {@code commit.cleanup = default} or is not set.
+ *
+ * @param strip
+ * if {@code true}, default to {@link CleanupMode#STRIP};
+ * otherwise default to {@link CleanupMode#WHITESPACE}
+ * @return {@code this}
+ * @since 6.1
+ */
+ public CommitCommand setDefaultClean(boolean strip) {
+ checkCallable();
+ this.cleanDefaultIsStrip = strip;
+ return this;
+ }
+
+ /**
+ * Sets the comment character to apply when cleaning a commit message. If
+ * {@code null} (the default) and the {@link #setCleanupMode(CleanupMode)
+ * clean-up mode} is {@link CleanupMode#STRIP} or
+ * {@link CleanupMode#SCISSORS}, the value of git config
+ * {@code core.commentChar} will be used.
+ *
+ * @param commentChar
+ * the comment character, or {@code null} to use the value from
+ * the git config
+ * @return {@code this}
+ * @since 6.1
+ */
+ public CommitCommand setCommentCharacter(Character commentChar) {
+ checkCallable();
+ this.commentChar = commentChar;
+ return this;
+ }
+
+ /**
* Set whether to allow to create an empty commit
*
* @param allowEmpty
@@ -806,7 +879,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
* command line.
*
* @param amend
- * whether to ammend the tip of the current branch
+ * whether to amend the tip of the current branch
* @return {@code this}
*/
public CommitCommand setAmend(boolean amend) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index 1e524fadab..805a886392 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -11,6 +11,7 @@ package org.eclipse.jgit.api;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
import java.io.IOException;
import java.text.MessageFormat;
@@ -33,6 +34,7 @@ import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.fnmatch.FileNameMatcher;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.AbbrevConfig;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -89,6 +91,11 @@ public class DescribeCommand extends GitCommand<String> {
private boolean always;
/**
+ * The prefix length to use when abbreviating a commit hash.
+ */
+ private int abbrev = UNSET_INT;
+
+ /**
* Constructor for DescribeCommand.
*
* @param repo
@@ -205,12 +212,33 @@ public class DescribeCommand extends GitCommand<String> {
return this;
}
+ /**
+ * Sets the prefix length to use when abbreviating an object SHA-1.
+ *
+ * @param abbrev
+ * minimum length of the abbreviated string. Must be in the range
+ * [{@value AbbrevConfig#MIN_ABBREV},
+ * {@value Constants#OBJECT_ID_STRING_LENGTH}].
+ * @return {@code this}
+ * @since 6.1
+ */
+ public DescribeCommand setAbbrev(int abbrev) {
+ if (abbrev == 0) {
+ this.abbrev = 0;
+ } else {
+ this.abbrev = AbbrevConfig.capAbbrev(abbrev);
+ }
+ return this;
+ }
+
private String longDescription(Ref tag, int depth, ObjectId tip)
throws IOException {
- return String.format(
- "%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
- Integer.valueOf(depth), w.getObjectReader().abbreviate(tip)
- .name());
+ if (abbrev == 0) {
+ return formatRefName(tag.getName());
+ }
+ return String.format("%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
+ Integer.valueOf(depth),
+ w.getObjectReader().abbreviate(tip, abbrev).name());
}
/**
@@ -302,6 +330,9 @@ public class DescribeCommand extends GitCommand<String> {
if (target == null) {
setTarget(Constants.HEAD);
}
+ if (abbrev == UNSET_INT) {
+ abbrev = AbbrevConfig.parseFromConfig(repo).get();
+ }
Collection<Ref> tagList = repo.getRefDatabase()
.getRefsByPrefix(useAll ? R_REFS : R_TAGS);
@@ -413,7 +444,12 @@ public class DescribeCommand extends GitCommand<String> {
// if all the nodes are dominated by all the tags, the walk stops
if (candidates.isEmpty()) {
- return always ? w.getObjectReader().abbreviate(target).name() : null;
+ return always
+ ? w.getObjectReader()
+ .abbreviate(target,
+ AbbrevConfig.capAbbrev(abbrev))
+ .name()
+ : null;
}
Candidate best = Collections.min(candidates,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
index ef56d802c8..ce068b6306 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -405,7 +405,7 @@ public class MergeCommand extends GitCommand<MergeResult> {
failingPaths, null);
}
String mergeMessageWithConflicts = new MergeMessageFormatter()
- .formatWithConflicts(mergeMessage, unmergedPaths);
+ .formatWithConflicts(mergeMessage, unmergedPaths, '#');
repo.writeMergeCommitMsg(mergeMessageWithConflicts);
return new MergeResult(null, merger.getBaseCommitId(),
new ObjectId[] { headCommit.getId(),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
index aa5a63499c..08353dfdfa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * Copyright (C) 2010, 2022 Chris Aniszczyk <caniszczyk@gmail.com> 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
@@ -21,7 +21,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.eclipse.jgit.api.errors.DetachedHeadException;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.NotSupportedException;
@@ -29,11 +31,16 @@ import org.eclipse.jgit.errors.TooLargeObjectInPackException;
import org.eclipse.jgit.errors.TooLargePackException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.BranchConfig;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushConfig;
+import org.eclipse.jgit.transport.PushConfig.PushDefault;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefLeaseSpec;
import org.eclipse.jgit.transport.RefSpec;
@@ -52,7 +59,7 @@ import org.eclipse.jgit.transport.Transport;
public class PushCommand extends
TransportCommand<PushCommand, Iterable<PushResult>> {
- private String remote = Constants.DEFAULT_REMOTE_NAME;
+ private String remote;
private final List<RefSpec> refSpecs;
@@ -71,6 +78,10 @@ public class PushCommand extends
private List<String> pushOptions;
+ // Legacy behavior as default. Use setPushDefault(null) to determine the
+ // value from the git config.
+ private PushDefault pushDefault = PushDefault.CURRENT;
+
/**
* <p>
* Constructor for PushCommand.
@@ -98,19 +109,20 @@ public class PushCommand extends
InvalidRemoteException,
org.eclipse.jgit.api.errors.TransportException {
checkCallable();
+ setCallable(false);
ArrayList<PushResult> pushResults = new ArrayList<>(3);
try {
+ Config config = repo.getConfig();
+ remote = determineRemote(config, remote);
if (refSpecs.isEmpty()) {
- RemoteConfig config = new RemoteConfig(repo.getConfig(),
+ RemoteConfig rc = new RemoteConfig(config,
getRemote());
- refSpecs.addAll(config.getPushRefSpecs());
- }
- if (refSpecs.isEmpty()) {
- Ref head = repo.exactRef(Constants.HEAD);
- if (head != null && head.isSymbolic())
- refSpecs.add(new RefSpec(head.getLeaf().getName()));
+ refSpecs.addAll(rc.getPushRefSpecs());
+ if (refSpecs.isEmpty()) {
+ determineDefaultRefSpecs(config);
+ }
}
if (force) {
@@ -118,8 +130,8 @@ public class PushCommand extends
refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
}
- final List<Transport> transports;
- transports = Transport.openAll(repo, remote, Transport.Operation.PUSH);
+ List<Transport> transports = Transport.openAll(repo, remote,
+ Transport.Operation.PUSH);
for (@SuppressWarnings("resource") // Explicitly closed in finally
final Transport transport : transports) {
transport.setPushThin(thin);
@@ -171,6 +183,102 @@ public class PushCommand extends
return pushResults;
}
+ private String determineRemote(Config config, String remoteName)
+ throws IOException {
+ if (remoteName != null) {
+ return remoteName;
+ }
+ Ref head = repo.exactRef(Constants.HEAD);
+ String effectiveRemote = null;
+ BranchConfig branchCfg = null;
+ if (head != null && head.isSymbolic()) {
+ String currentBranch = head.getLeaf().getName();
+ branchCfg = new BranchConfig(config,
+ Repository.shortenRefName(currentBranch));
+ effectiveRemote = branchCfg.getPushRemote();
+ }
+ if (effectiveRemote == null) {
+ effectiveRemote = config.getString(
+ ConfigConstants.CONFIG_REMOTE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_PUSH_DEFAULT);
+ if (effectiveRemote == null && branchCfg != null) {
+ effectiveRemote = branchCfg.getRemote();
+ }
+ }
+ if (effectiveRemote == null) {
+ effectiveRemote = Constants.DEFAULT_REMOTE_NAME;
+ }
+ return effectiveRemote;
+ }
+
+ private String getCurrentBranch()
+ throws IOException, DetachedHeadException {
+ Ref head = repo.exactRef(Constants.HEAD);
+ if (head != null && head.isSymbolic()) {
+ return head.getLeaf().getName();
+ }
+ throw new DetachedHeadException();
+ }
+
+ private void determineDefaultRefSpecs(Config config)
+ throws IOException, GitAPIException {
+ if (pushDefault == null) {
+ pushDefault = config.get(PushConfig::new).getPushDefault();
+ }
+ switch (pushDefault) {
+ case CURRENT:
+ refSpecs.add(new RefSpec(getCurrentBranch()));
+ break;
+ case MATCHING:
+ refSpecs.add(new RefSpec(":")); //$NON-NLS-1$
+ break;
+ case NOTHING:
+ throw new InvalidRefNameException(
+ JGitText.get().pushDefaultNothing);
+ case SIMPLE:
+ case UPSTREAM:
+ String currentBranch = getCurrentBranch();
+ BranchConfig branchCfg = new BranchConfig(config,
+ Repository.shortenRefName(currentBranch));
+ String fetchRemote = branchCfg.getRemote();
+ if (fetchRemote == null) {
+ fetchRemote = Constants.DEFAULT_REMOTE_NAME;
+ }
+ boolean isTriangular = !fetchRemote.equals(remote);
+ if (isTriangular) {
+ if (PushDefault.UPSTREAM.equals(pushDefault)) {
+ throw new InvalidRefNameException(MessageFormat.format(
+ JGitText.get().pushDefaultTriangularUpstream,
+ remote, fetchRemote));
+ }
+ // Strange, but consistent with C git: "simple" doesn't even
+ // check whether there is a configured upstream, and if so, that
+ // it is equal to the local branch name. It just becomes
+ // "current".
+ refSpecs.add(new RefSpec(currentBranch));
+ } else {
+ String trackedBranch = branchCfg.getMerge();
+ if (branchCfg.isRemoteLocal() || trackedBranch == null
+ || !trackedBranch.startsWith(Constants.R_HEADS)) {
+ throw new InvalidRefNameException(MessageFormat.format(
+ JGitText.get().pushDefaultNoUpstream,
+ currentBranch));
+ }
+ if (PushDefault.SIMPLE.equals(pushDefault)
+ && !trackedBranch.equals(currentBranch)) {
+ throw new InvalidRefNameException(MessageFormat.format(
+ JGitText.get().pushDefaultSimple, currentBranch,
+ trackedBranch));
+ }
+ refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch));
+ }
+ break;
+ default:
+ throw new InvalidRefNameException(MessageFormat
+ .format(JGitText.get().pushDefaultUnknown, pushDefault));
+ }
+ }
+
/**
* The remote (uri or name) used for the push operation. If no remote is
* set, the default value of <code>Constants.DEFAULT_REMOTE_NAME</code> will
@@ -336,9 +444,37 @@ public class PushCommand extends
}
/**
+ * Retrieves the {@link PushDefault} currently set.
+ *
+ * @return the {@link PushDefault}, or {@code null} if not set
+ * @since 6.1
+ */
+ public PushDefault getPushDefault() {
+ return pushDefault;
+ }
+
+ /**
+ * Sets an explicit {@link PushDefault}. The default used if this is not
+ * called is {@link PushDefault#CURRENT} for compatibility reasons with
+ * earlier JGit versions.
+ *
+ * @param pushDefault
+ * {@link PushDefault} to set; if {@code null} the value defined
+ * in the git config will be used.
+ *
+ * @return {@code this}
+ * @since 6.1
+ */
+ public PushCommand setPushDefault(PushDefault pushDefault) {
+ checkCallable();
+ this.pushDefault = pushDefault;
+ return this;
+ }
+
+ /**
* Push all branches under refs/heads/*.
*
- * @return {code this}
+ * @return {@code this}
*/
public PushCommand setPushAll() {
refSpecs.add(Transport.REFSPEC_PUSH_ALL);
@@ -348,7 +484,7 @@ public class PushCommand extends
/**
* Push all tags under refs/tags/*.
*
- * @return {code this}
+ * @return {@code this}
*/
public PushCommand setPushTags() {
refSpecs.add(Transport.REFSPEC_TAGS);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index a26ffc2e66..2b0d8ce1c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -29,6 +29,7 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.api.RebaseResult.Status;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
@@ -52,6 +53,8 @@ import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.CommitConfig;
+import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -205,6 +208,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
private InteractiveHandler interactiveHandler;
+ private CommitConfig commitConfig;
+
private boolean stopAfterInitialization = false;
private RevCommit newHead;
@@ -246,6 +251,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
lastStepWasForward = false;
checkCallable();
checkParameters();
+ commitConfig = repo.getConfig().get(CommitConfig.KEY);
try {
switch (operation) {
case ABORT:
@@ -441,11 +447,16 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return null; // continue rebase process on pick command
case REWORD:
String oldMessage = commitToPick.getFullMessage();
- String newMessage = interactiveHandler
- .modifyCommitMessage(oldMessage);
+ CleanupMode mode = commitConfig.resolve(CleanupMode.DEFAULT, true);
+ boolean[] doChangeId = { false };
+ String newMessage = editCommitMessage(doChangeId, oldMessage, mode);
try (Git git = new Git(repo)) {
- newHead = git.commit().setMessage(newMessage).setAmend(true)
- .setNoVerify(true).call();
+ newHead = git.commit()
+ .setMessage(newMessage)
+ .setAmend(true)
+ .setNoVerify(true)
+ .setInsertChangeId(doChangeId[0])
+ .call();
}
return null;
case EDIT:
@@ -460,17 +471,49 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
resetSoftToParent();
List<RebaseTodoLine> steps = repo.readRebaseTodo(
rebaseState.getPath(GIT_REBASE_TODO), false);
- RebaseTodoLine nextStep = steps.isEmpty() ? null : steps.get(0);
+ boolean isLast = steps.isEmpty();
+ if (!isLast) {
+ switch (steps.get(0).getAction()) {
+ case FIXUP:
+ case SQUASH:
+ break;
+ default:
+ isLast = true;
+ break;
+ }
+ }
File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP);
File messageSquashFile = rebaseState.getFile(MESSAGE_SQUASH);
- if (isSquash && messageFixupFile.exists())
+ if (isSquash && messageFixupFile.exists()) {
messageFixupFile.delete();
- newHead = doSquashFixup(isSquash, commitToPick, nextStep,
+ }
+ newHead = doSquashFixup(isSquash, commitToPick, isLast,
messageFixupFile, messageSquashFile);
}
return null;
}
+ private String editCommitMessage(boolean[] doChangeId, String message,
+ @NonNull CleanupMode mode) {
+ String newMessage;
+ CommitConfig.CleanupMode cleanup;
+ if (interactiveHandler instanceof InteractiveHandler2) {
+ InteractiveHandler2.ModifyResult modification = ((InteractiveHandler2) interactiveHandler)
+ .editCommitMessage(message, mode, '#');
+ newMessage = modification.getMessage();
+ cleanup = modification.getCleanupMode();
+ if (CleanupMode.DEFAULT.equals(cleanup)) {
+ cleanup = mode;
+ }
+ doChangeId[0] = modification.shouldAddChangeId();
+ } else {
+ newMessage = interactiveHandler.modifyCommitMessage(message);
+ cleanup = CommitConfig.CleanupMode.STRIP;
+ doChangeId[0] = false;
+ }
+ return CommitConfig.cleanText(newMessage, cleanup, '#');
+ }
+
private RebaseResult cherryPickCommit(RevCommit commitToPick)
throws IOException, GitAPIException, NoMessageException,
UnmergedPathsException, ConcurrentRefUpdateException,
@@ -707,7 +750,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
private RevCommit doSquashFixup(boolean isSquash, RevCommit commitToPick,
- RebaseTodoLine nextStep, File messageFixup, File messageSquash)
+ boolean isLast, File messageFixup, File messageSquash)
throws IOException, GitAPIException {
if (!messageSquash.exists()) {
@@ -717,24 +760,20 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
initializeSquashFixupFile(MESSAGE_SQUASH,
previousCommit.getFullMessage());
- if (!isSquash)
- initializeSquashFixupFile(MESSAGE_FIXUP,
- previousCommit.getFullMessage());
+ if (!isSquash) {
+ rebaseState.createFile(MESSAGE_FIXUP,
+ previousCommit.getFullMessage());
+ }
}
- String currSquashMessage = rebaseState
- .readFile(MESSAGE_SQUASH);
+ String currSquashMessage = rebaseState.readFile(MESSAGE_SQUASH);
int count = parseSquashFixupSequenceCount(currSquashMessage) + 1;
String content = composeSquashMessage(isSquash,
commitToPick, currSquashMessage, count);
rebaseState.createFile(MESSAGE_SQUASH, content);
- if (messageFixup.exists())
- rebaseState.createFile(MESSAGE_FIXUP, content);
- return squashIntoPrevious(
- !messageFixup.exists(),
- nextStep);
+ return squashIntoPrevious(!messageFixup.exists(), isLast);
}
private void resetSoftToParent() throws IOException,
@@ -756,26 +795,30 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
private RevCommit squashIntoPrevious(boolean sequenceContainsSquash,
- RebaseTodoLine nextStep)
+ boolean isLast)
throws IOException, GitAPIException {
RevCommit retNewHead;
- String commitMessage = rebaseState
- .readFile(MESSAGE_SQUASH);
-
+ String commitMessage;
+ if (!isLast || sequenceContainsSquash) {
+ commitMessage = rebaseState.readFile(MESSAGE_SQUASH);
+ } else {
+ commitMessage = rebaseState.readFile(MESSAGE_FIXUP);
+ }
try (Git git = new Git(repo)) {
- if (nextStep == null || ((nextStep.getAction() != Action.FIXUP)
- && (nextStep.getAction() != Action.SQUASH))) {
- // this is the last step in this sequence
+ if (isLast) {
+ boolean[] doChangeId = { false };
if (sequenceContainsSquash) {
- commitMessage = interactiveHandler
- .modifyCommitMessage(commitMessage);
+ commitMessage = editCommitMessage(doChangeId, commitMessage,
+ CleanupMode.STRIP);
}
retNewHead = git.commit()
- .setMessage(stripCommentLines(commitMessage))
- .setAmend(true).setNoVerify(true).call();
+ .setMessage(commitMessage)
+ .setAmend(true)
+ .setNoVerify(true)
+ .setInsertChangeId(doChangeId[0])
+ .call();
rebaseState.getFile(MESSAGE_SQUASH).delete();
rebaseState.getFile(MESSAGE_FIXUP).delete();
-
} else {
// Next step is either Squash or Fixup
retNewHead = git.commit().setMessage(commitMessage)
@@ -785,21 +828,6 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return retNewHead;
}
- private static String stripCommentLines(String commitMessage) {
- StringBuilder result = new StringBuilder();
- for (String line : commitMessage.split("\n")) { //$NON-NLS-1$
- if (!line.trim().startsWith("#")) //$NON-NLS-1$
- result.append(line).append("\n"); //$NON-NLS-1$
- }
- if (!commitMessage.endsWith("\n")) { //$NON-NLS-1$
- int bufferSize = result.length();
- if (bufferSize > 0 && result.charAt(bufferSize - 1) == '\n') {
- result.deleteCharAt(bufferSize - 1);
- }
- }
- return result.toString();
- }
-
@SuppressWarnings("nls")
private static String composeSquashMessage(boolean isSquash,
RevCommit commitToPick, String currSquashMessage, int count) {
@@ -1625,26 +1653,106 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
/**
- * Allows configure rebase interactive process and modify commit message
+ * Allows to configure the interactive rebase process steps and to modify
+ * commit messages.
*/
public interface InteractiveHandler {
+
/**
- * Given list of {@code steps} should be modified according to user
- * rebase configuration
+ * Callback API to modify the initial list of interactive rebase steps.
+ *
* @param steps
- * initial configuration of rebase interactive
+ * initial configuration of interactive rebase
*/
void prepareSteps(List<RebaseTodoLine> steps);
/**
- * Used for editing commit message on REWORD
+ * Used for editing commit message on REWORD or SQUASH.
*
- * @param commit
+ * @param message
+ * existing commit message
* @return new commit message
*/
- String modifyCommitMessage(String commit);
+ String modifyCommitMessage(String message);
}
+ /**
+ * Extends {@link InteractiveHandler} with an enhanced callback for editing
+ * commit messages.
+ *
+ * @since 6.1
+ */
+ public interface InteractiveHandler2 extends InteractiveHandler {
+
+ /**
+ * Callback API for editing a commit message on REWORD or SQUASH.
+ * <p>
+ * The callback gets the comment character currently set, and the
+ * clean-up mode. It can use this information when presenting the
+ * message to the user, and it also has the possibility to clean the
+ * message itself (in which case the returned {@link ModifyResult}
+ * should have {@link CleanupMode#VERBATIM} set lest JGit cleans the
+ * message again). It can also override the initial clean-up mode by
+ * returning clean-up mode other than {@link CleanupMode#DEFAULT}. If it
+ * does return {@code DEFAULT}, the passed-in {@code mode} will be
+ * applied.
+ * </p>
+ *
+ * @param message
+ * existing commit message
+ * @param mode
+ * {@link CleanupMode} currently set
+ * @param commentChar
+ * comment character used
+ * @return a {@link ModifyResult}
+ */
+ @NonNull
+ ModifyResult editCommitMessage(@NonNull String message,
+ @NonNull CleanupMode mode, char commentChar);
+
+ @Override
+ default String modifyCommitMessage(String message) {
+ // Should actually not be called; but do something reasonable anyway
+ ModifyResult result = editCommitMessage(
+ message == null ? "" : message, CleanupMode.STRIP, //$NON-NLS-1$
+ '#');
+ return result.getMessage();
+ }
+
+ /**
+ * Describes the result of editing a commit message: the new message,
+ * and how it should be cleaned.
+ */
+ interface ModifyResult {
+
+ /**
+ * Retrieves the new commit message.
+ *
+ * @return the message
+ */
+ @NonNull
+ String getMessage();
+
+ /**
+ * Tells how the message returned by {@link #getMessage()} should be
+ * cleaned.
+ *
+ * @return the {@link CleanupMode}
+ */
+ @NonNull
+ CleanupMode getCleanupMode();
+
+ /**
+ * Tells whether a Gerrit Change-Id should be computed and added to
+ * the commit message, as with
+ * {@link CommitCommand#setInsertChangeId(boolean)}.
+ *
+ * @return {@code true} if a Change-Id should be handled,
+ * {@code false} otherwise
+ */
+ boolean shouldAddChangeId();
+ }
+ }
PersonIdent parseAuthor(byte[] raw) {
if (raw.length == 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
index 22ef4d0a32..db88ad8dc9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -9,6 +9,8 @@
*/
package org.eclipse.jgit.api;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
import java.io.IOException;
import java.text.MessageFormat;
import java.util.LinkedList;
@@ -128,8 +130,9 @@ public class RevertCommand extends GitCommand<RevCommit> {
revWalk.parseHeaders(srcParent);
String ourName = calculateOurName(headRef);
- String revertName = srcCommit.getId().abbreviate(7).name()
- + " " + srcCommit.getShortMessage(); //$NON-NLS-1$
+ String revertName = srcCommit.getId()
+ .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + " " //$NON-NLS-1$
+ + srcCommit.getShortMessage();
ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
merger.setWorkingTreeIterator(new FileTreeIterator(repo));
@@ -183,8 +186,8 @@ public class RevertCommand extends GitCommand<RevCommit> {
merger.getMergeResults(), failingPaths, null);
if (!merger.failed() && !unmergedPaths.isEmpty()) {
String message = new MergeMessageFormatter()
- .formatWithConflicts(newMessage,
- merger.getUnmergedPaths());
+ .formatWithConflicts(newMessage,
+ merger.getUnmergedPaths(), '#');
repo.writeRevertHead(srcCommit.getId());
repo.writeMergeCommitMsg(message);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index 35fd8992b6..f7a1f4eff8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -9,6 +9,8 @@
*/
package org.eclipse.jgit.api;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -302,7 +304,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
builder.setParentId(headCommit);
builder.setTreeId(cache.writeTree(inserter));
builder.setMessage(MessageFormat.format(indexMessage, branch,
- headCommit.abbreviate(7).name(),
+ headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+ .name(),
headCommit.getShortMessage()));
ObjectId indexCommit = inserter.insert(builder);
@@ -319,7 +322,10 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
builder.setParentIds(new ObjectId[0]);
builder.setTreeId(untrackedDirCache.writeTree(inserter));
builder.setMessage(MessageFormat.format(MSG_UNTRACKED,
- branch, headCommit.abbreviate(7).name(),
+ branch,
+ headCommit
+ .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+ .name(),
headCommit.getShortMessage()));
untrackedCommit = inserter.insert(builder);
}
@@ -339,7 +345,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
builder.addParentId(untrackedCommit);
builder.setMessage(MessageFormat.format(
workingDirectoryMessage, branch,
- headCommit.abbreviate(7).name(),
+ headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+ .name(),
headCommit.getShortMessage()));
builder.setTreeId(cache.writeTree(inserter));
commitId = inserter.insert(builder);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
index 638dd827ed..7ec78597fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
@@ -1,43 +1,11 @@
/*
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * Copyright (C) 2015, 2022 Ivan Motsch <ivan.motsch@bsiag.com> and others
*
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
+ * 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.
*
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.attributes;
@@ -46,6 +14,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.function.Supplier;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.attributes.Attribute.State;
@@ -84,6 +53,8 @@ public class AttributesHandler {
private final TreeWalk treeWalk;
+ private final Supplier<CanonicalTreeParser> attributesTree;
+
private final AttributesNode globalNode;
private final AttributesNode infoNode;
@@ -98,22 +69,41 @@ public class AttributesHandler {
* @param treeWalk
* a {@link org.eclipse.jgit.treewalk.TreeWalk}
* @throws java.io.IOException
+ * @deprecated since 6.1, use {@link #AttributesHandler(TreeWalk, Supplier)}
+ * instead
*/
+ @Deprecated
public AttributesHandler(TreeWalk treeWalk) throws IOException {
+ this(treeWalk, () -> treeWalk.getTree(CanonicalTreeParser.class));
+ }
+
+ /**
+ * Create an {@link org.eclipse.jgit.attributes.AttributesHandler} with
+ * default rules as well as merged rules from global, info and worktree root
+ * attributes
+ *
+ * @param treeWalk
+ * a {@link org.eclipse.jgit.treewalk.TreeWalk}
+ * @param attributesTree
+ * the tree to read .gitattributes from
+ * @throws java.io.IOException
+ * @since 6.1
+ */
+ public AttributesHandler(TreeWalk treeWalk,
+ Supplier<CanonicalTreeParser> attributesTree) throws IOException {
this.treeWalk = treeWalk;
- AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider();
+ this.attributesTree = attributesTree;
+ AttributesNodeProvider attributesNodeProvider = treeWalk
+ .getAttributesNodeProvider();
this.globalNode = attributesNodeProvider != null
? attributesNodeProvider.getGlobalAttributesNode() : null;
this.infoNode = attributesNodeProvider != null
? attributesNodeProvider.getInfoAttributesNode() : null;
AttributesNode rootNode = attributesNode(treeWalk,
- rootOf(
- treeWalk.getTree(WorkingTreeIterator.class)),
- rootOf(
- treeWalk.getTree(DirCacheIterator.class)),
- rootOf(treeWalk
- .getTree(CanonicalTreeParser.class)));
+ rootOf(treeWalk.getTree(WorkingTreeIterator.class)),
+ rootOf(treeWalk.getTree(DirCacheIterator.class)),
+ rootOf(attributesTree.get()));
expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
@@ -152,7 +142,7 @@ public class AttributesHandler {
isDirectory,
treeWalk.getTree(WorkingTreeIterator.class),
treeWalk.getTree(DirCacheIterator.class),
- treeWalk.getTree(CanonicalTreeParser.class),
+ attributesTree.get(),
attributes);
// Gets the attributes located in the global attribute file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index 49da95c9ab..1a5f74f98a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -18,6 +18,7 @@ import static org.eclipse.jgit.diff.DiffEntry.ChangeType.MODIFY;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
import static org.eclipse.jgit.diff.DiffEntry.Side.NEW;
import static org.eclipse.jgit.diff.DiffEntry.Side.OLD;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
import static org.eclipse.jgit.lib.Constants.encode;
import static org.eclipse.jgit.lib.Constants.encodeASCII;
import static org.eclipse.jgit.lib.FileMode.GITLINK;
@@ -90,7 +91,7 @@ public class DiffFormatter implements AutoCloseable {
private int context = 3;
- private int abbreviationLength = 7;
+ private int abbreviationLength = OBJECT_ID_ABBREV_STRING_LENGTH;
private DiffAlgorithm diffAlgorithm;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index c904a782db..3d50a82155 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -4,7 +4,8 @@
* Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
* Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
- * Copyright (C) 2019-2020, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com>
+ * Copyright (C) 2017, 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
@@ -299,7 +300,7 @@ public class DirCacheCheckout {
walk = new NameConflictTreeWalk(repo);
builder = dc.builder();
- addTree(walk, headCommitTree);
+ walk.setHead(addTree(walk, headCommitTree));
addTree(walk, mergeCommitTree);
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
walk.addTree(workingTree);
@@ -315,13 +316,6 @@ public class DirCacheCheckout {
}
}
- private void addTree(TreeWalk tw, ObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException {
- if (id == null)
- tw.addTree(new EmptyTreeIterator());
- else
- tw.addTree(id);
- }
-
/**
* Scan index and merge tree (no HEAD). Used e.g. for initial checkout when
* there is no head yet.
@@ -341,7 +335,7 @@ public class DirCacheCheckout {
builder = dc.builder();
walk = new NameConflictTreeWalk(repo);
- addTree(walk, mergeCommitTree);
+ walk.setHead(addTree(walk, mergeCommitTree));
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
walk.addTree(workingTree);
workingTree.setDirCacheIterator(walk, dciPos);
@@ -356,6 +350,14 @@ public class DirCacheCheckout {
conflicts.removeAll(removed);
}
+ private int addTree(TreeWalk tw, ObjectId id) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if (id == null) {
+ return tw.addTree(new EmptyTreeIterator());
+ }
+ return tw.addTree(id);
+ }
+
/**
* Processing an entry in the context of {@link #prescanOneTree()} when only
* one tree is given
@@ -382,17 +384,14 @@ public class DirCacheCheckout {
// failOnConflict is false. Putting something to conflicts
// would mean we delete it. Instead we want the mergeCommit
// content to be checked out.
- update(m.getEntryPathString(), m.getEntryObjectId(),
- m.getEntryFileMode());
+ update(m);
}
} else
- update(m.getEntryPathString(), m.getEntryObjectId(),
- m.getEntryFileMode());
+ update(m);
} else if (f == null || !m.idEqual(i)) {
// The working tree file is missing or the merge content differs
// from index content
- update(m.getEntryPathString(), m.getEntryObjectId(),
- m.getEntryFileMode());
+ update(m);
} else if (i.getDirCacheEntry() != null) {
// The index contains a file (and not a folder)
if (f.isModified(i.getDirCacheEntry(), true,
@@ -400,8 +399,7 @@ public class DirCacheCheckout {
|| i.getDirCacheEntry().getStage() != 0)
// The working tree file is dirty or the index contains a
// conflict
- update(m.getEntryPathString(), m.getEntryObjectId(),
- m.getEntryFileMode());
+ update(m);
else {
// update the timestamp of the index with the one from the
// file if not set, as we are sure to be in sync here.
@@ -802,7 +800,7 @@ public class DirCacheCheckout {
if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
conflict(name, dce, h, m); // 1
} else {
- update(name, mId, mMode); // 2
+ update(1, name, mId, mMode); // 2
}
break;
@@ -828,7 +826,7 @@ public class DirCacheCheckout {
// are found later
break;
case 0xD0F: // 19
- update(name, mId, mMode);
+ update(1, name, mId, mMode);
break;
case 0xDF0: // conflict without a rule
case 0x0FD: // 15
@@ -839,7 +837,7 @@ public class DirCacheCheckout {
if (isModifiedSubtree_IndexWorkingtree(name))
conflict(name, dce, h, m); // 8
else
- update(name, mId, mMode); // 7
+ update(1, name, mId, mMode); // 7
} else
conflict(name, dce, h, m); // 9
break;
@@ -859,7 +857,7 @@ public class DirCacheCheckout {
break;
case 0x0DF: // 16 17
if (!isModifiedSubtree_IndexWorkingtree(name))
- update(name, mId, mMode);
+ update(1, name, mId, mMode);
else
conflict(name, dce, h, m);
break;
@@ -929,7 +927,7 @@ public class DirCacheCheckout {
// At least one of Head, Index, Merge is not empty
// -> only Merge contains something for this path. Use it!
// Potentially update the file
- update(name, mId, mMode); // 1
+ update(1, name, mId, mMode); // 1
else if (m == null)
// Nothing in Merge
// Something in Head
@@ -947,7 +945,7 @@ public class DirCacheCheckout {
// find in Merge. Potentially updates the file.
if (equalIdAndMode(hId, hMode, mId, mMode)) {
if (initialCheckout || force) {
- update(name, mId, mMode);
+ update(1, name, mId, mMode);
} else {
keep(name, dce, f);
}
@@ -1131,7 +1129,7 @@ public class DirCacheCheckout {
// TODO check that we don't overwrite some unsaved
// file content
- update(name, mId, mMode);
+ update(1, name, mId, mMode);
} else if (dce != null
&& (f != null && f.isModified(dce, true,
this.walk.getObjectReader()))) {
@@ -1150,7 +1148,7 @@ public class DirCacheCheckout {
// -> Standard case when switching between branches:
// Nothing new in index but something different in
// Merge. Update index and file
- update(name, mId, mMode);
+ update(1, name, mId, mMode);
}
} else {
// Head differs from index or merge is same as index
@@ -1237,12 +1235,17 @@ public class DirCacheCheckout {
removed.add(path);
}
- private void update(String path, ObjectId mId, FileMode mode)
- throws IOException {
+ private void update(CanonicalTreeParser tree) throws IOException {
+ update(0, tree.getEntryPathString(), tree.getEntryObjectId(),
+ tree.getEntryFileMode());
+ }
+
+ private void update(int index, String path, ObjectId mId,
+ FileMode mode) throws IOException {
if (!FileMode.TREE.equals(mode)) {
updated.put(path, new CheckoutMetadata(
- walk.getEolStreamType(CHECKOUT_OP),
- walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
+ walk.getCheckoutEolStreamType(index),
+ walk.getSmudgeCommand(index)));
DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
entry.setObjectId(mId);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
new file mode 100644
index 0000000000..e6626aece3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2021, Google Inc. 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.gitrepo;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+
+import java.io.IOException;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.List;
+
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.gitrepo.RepoCommand.ManifestErrorException;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteFile;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader;
+import org.eclipse.jgit.gitrepo.RepoCommand.RemoteUnavailableException;
+import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
+import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
+import org.eclipse.jgit.gitrepo.internal.RepoText;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.FileUtils;
+
+/**
+ * Writes .gitmodules and gitlinks of parsed manifest projects into a bare
+ * repository.
+ *
+ * To write on a regular repository, see {@link RegularSuperprojectWriter}.
+ */
+class BareSuperprojectWriter {
+ private static final int LOCK_FAILURE_MAX_RETRIES = 5;
+
+ // Retry exponentially with delays in this range
+ private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50;
+
+ private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000;
+
+ private final Repository repo;
+
+ private final URI targetUri;
+
+ private final String targetBranch;
+
+ private final RemoteReader callback;
+
+ private final BareWriterConfig config;
+
+ private final PersonIdent author;
+
+ private List<ExtraContent> extraContents;
+
+ static class BareWriterConfig {
+ boolean ignoreRemoteFailures = false;
+
+ boolean recordRemoteBranch = true;
+
+ boolean recordSubmoduleLabels = true;
+
+ boolean recordShallowSubmodules = true;
+
+ static BareWriterConfig getDefault() {
+ return new BareWriterConfig();
+ }
+
+ private BareWriterConfig() {
+ }
+ }
+
+ static class ExtraContent {
+ final String path;
+
+ final String content;
+
+ ExtraContent(String path, String content) {
+ this.path = path;
+ this.content = content;
+ }
+ }
+
+ BareSuperprojectWriter(Repository repo, URI targetUri,
+ String targetBranch,
+ PersonIdent author, RemoteReader callback,
+ BareWriterConfig config,
+ List<ExtraContent> extraContents) {
+ assert (repo.isBare());
+ this.repo = repo;
+ this.targetUri = targetUri;
+ this.targetBranch = targetBranch;
+ this.author = author;
+ this.callback = callback;
+ this.config = config;
+ this.extraContents = extraContents;
+ }
+
+ RevCommit write(List<RepoProject> repoProjects)
+ throws GitAPIException {
+ DirCache index = DirCache.newInCore();
+ ObjectInserter inserter = repo.newObjectInserter();
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ prepareIndex(repoProjects, index, inserter);
+ ObjectId treeId = index.writeTree(inserter);
+ long prevDelay = 0;
+ for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) {
+ try {
+ return commitTreeOnCurrentTip(inserter, rw, treeId);
+ } catch (ConcurrentRefUpdateException e) {
+ prevDelay = FileUtils.delay(prevDelay,
+ LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS,
+ LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS);
+ Thread.sleep(prevDelay);
+ repo.getRefDatabase().refresh();
+ }
+ }
+ // In the last try, just propagate the exceptions
+ return commitTreeOnCurrentTip(inserter, rw, treeId);
+ } catch (IOException | InterruptedException e) {
+ throw new ManifestErrorException(e);
+ }
+ }
+
+ private void prepareIndex(List<RepoProject> projects, DirCache index,
+ ObjectInserter inserter) throws IOException, GitAPIException {
+ Config cfg = new Config();
+ StringBuilder attributes = new StringBuilder();
+ DirCacheBuilder builder = index.builder();
+ for (RepoProject proj : projects) {
+ String name = proj.getName();
+ String path = proj.getPath();
+ String url = proj.getUrl();
+ ObjectId objectId;
+ if (ObjectId.isId(proj.getRevision())) {
+ objectId = ObjectId.fromString(proj.getRevision());
+ } else {
+ objectId = callback.sha1(url, proj.getRevision());
+ if (objectId == null && !config.ignoreRemoteFailures) {
+ throw new RemoteUnavailableException(url);
+ }
+ if (config.recordRemoteBranch) {
+ // "branch" field is only for non-tag references.
+ // Keep tags in "ref" field as hint for other tools.
+ String field = proj.getRevision().startsWith(R_TAGS) ? "ref" //$NON-NLS-1$
+ : "branch"; //$NON-NLS-1$
+ cfg.setString("submodule", name, field, //$NON-NLS-1$
+ proj.getRevision());
+ }
+
+ if (config.recordShallowSubmodules
+ && proj.getRecommendShallow() != null) {
+ // The shallow recommendation is losing information.
+ // As the repo manifests stores the recommended
+ // depth in the 'clone-depth' field, while
+ // git core only uses a binary 'shallow = true/false'
+ // hint, we'll map any depth to 'shallow = true'
+ cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
+ true);
+ }
+ }
+ if (config.recordSubmoduleLabels) {
+ StringBuilder rec = new StringBuilder();
+ rec.append("/"); //$NON-NLS-1$
+ rec.append(path);
+ for (String group : proj.getGroups()) {
+ rec.append(" "); //$NON-NLS-1$
+ rec.append(group);
+ }
+ rec.append("\n"); //$NON-NLS-1$
+ attributes.append(rec.toString());
+ }
+
+ URI submodUrl = URI.create(url);
+ if (targetUri != null) {
+ submodUrl = RepoCommand.relativize(targetUri, submodUrl);
+ }
+ cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
+ cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$
+ submodUrl.toString());
+
+ // create gitlink
+ if (objectId != null) {
+ DirCacheEntry dcEntry = new DirCacheEntry(path);
+ dcEntry.setObjectId(objectId);
+ dcEntry.setFileMode(FileMode.GITLINK);
+ builder.add(dcEntry);
+
+ for (CopyFile copyfile : proj.getCopyFiles()) {
+ RemoteFile rf = callback.readFileWithMode(url,
+ proj.getRevision(), copyfile.src);
+ objectId = inserter.insert(Constants.OBJ_BLOB,
+ rf.getContents());
+ dcEntry = new DirCacheEntry(copyfile.dest);
+ dcEntry.setObjectId(objectId);
+ dcEntry.setFileMode(rf.getFileMode());
+ builder.add(dcEntry);
+ }
+ for (LinkFile linkfile : proj.getLinkFiles()) {
+ String link;
+ if (linkfile.dest.contains("/")) { //$NON-NLS-1$
+ link = FileUtils.relativizeGitPath(
+ linkfile.dest.substring(0,
+ linkfile.dest.lastIndexOf('/')),
+ proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$
+ } else {
+ link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$
+ }
+
+ objectId = inserter.insert(Constants.OBJ_BLOB,
+ link.getBytes(UTF_8));
+ dcEntry = new DirCacheEntry(linkfile.dest);
+ dcEntry.setObjectId(objectId);
+ dcEntry.setFileMode(FileMode.SYMLINK);
+ builder.add(dcEntry);
+ }
+ }
+ }
+ String content = cfg.toText();
+
+ // create a new DirCacheEntry for .gitmodules file.
+ DirCacheEntry dcEntry = new DirCacheEntry(
+ Constants.DOT_GIT_MODULES);
+ ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
+ content.getBytes(UTF_8));
+ dcEntry.setObjectId(objectId);
+ dcEntry.setFileMode(FileMode.REGULAR_FILE);
+ builder.add(dcEntry);
+
+ if (config.recordSubmoduleLabels) {
+ // create a new DirCacheEntry for .gitattributes file.
+ DirCacheEntry dcEntryAttr = new DirCacheEntry(
+ Constants.DOT_GIT_ATTRIBUTES);
+ ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
+ attributes.toString().getBytes(UTF_8));
+ dcEntryAttr.setObjectId(attrId);
+ dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
+ builder.add(dcEntryAttr);
+ }
+
+ for (ExtraContent ec : extraContents) {
+ DirCacheEntry extraDcEntry = new DirCacheEntry(ec.path);
+
+ ObjectId oid = inserter.insert(Constants.OBJ_BLOB,
+ ec.content.getBytes(UTF_8));
+ extraDcEntry.setObjectId(oid);
+ extraDcEntry.setFileMode(FileMode.REGULAR_FILE);
+ builder.add(extraDcEntry);
+ }
+
+ builder.finish();
+ }
+
+ private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter,
+ RevWalk rw, ObjectId treeId)
+ throws IOException, ConcurrentRefUpdateException {
+ ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$
+ if (headId != null
+ && rw.parseCommit(headId).getTree().getId().equals(treeId)) {
+ // No change. Do nothing.
+ return rw.parseCommit(headId);
+ }
+
+ CommitBuilder commit = new CommitBuilder();
+ commit.setTreeId(treeId);
+ if (headId != null) {
+ commit.setParentIds(headId);
+ }
+ commit.setAuthor(author);
+ commit.setCommitter(author);
+ commit.setMessage(RepoText.get().repoCommitMessage);
+
+ ObjectId commitId = inserter.insert(commit);
+ inserter.flush();
+
+ RefUpdate ru = repo.updateRef(targetBranch);
+ ru.setNewObjectId(commitId);
+ ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
+ Result rc = ru.update(rw);
+ switch (rc) {
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD:
+ // Successful. Do nothing.
+ break;
+ case REJECTED:
+ case LOCK_FAILURE:
+ throw new ConcurrentRefUpdateException(MessageFormat.format(
+ JGitText.get().cannotLock, targetBranch), ru.getRef(), rc);
+ default:
+ throw new JGitInternalException(
+ MessageFormat.format(JGitText.get().updatingRefFailed,
+ targetBranch, commitId.name(), rc));
+ }
+
+ return rw.parseCommit(commitId);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java
new file mode 100644
index 0000000000..afab9943a7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021, Google Inc. 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.gitrepo;
+
+import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.SubmoduleAddCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.gitrepo.RepoCommand.ManifestErrorException;
+import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
+import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
+import org.eclipse.jgit.gitrepo.internal.RepoText;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * Writes .gitmodules and gitlinks of parsed manifest projects into a regular
+ * repository (using git submodule commands)
+ *
+ * To write on a bare repository, use {@link BareSuperprojectWriter}
+ */
+class RegularSuperprojectWriter {
+
+ private Repository repo;
+
+ private ProgressMonitor monitor;
+
+ RegularSuperprojectWriter(Repository repo, ProgressMonitor monitor) {
+ this.repo = repo;
+ this.monitor = monitor;
+ }
+
+ RevCommit write(List<RepoProject> repoProjects)
+ throws GitAPIException {
+ try (Git git = new Git(repo)) {
+ for (RepoProject proj : repoProjects) {
+ addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
+ proj.getRevision(), proj.getCopyFiles(),
+ proj.getLinkFiles(), git);
+ }
+ return git.commit().setMessage(RepoText.get().repoCommitMessage)
+ .call();
+ } catch (IOException e) {
+ throw new ManifestErrorException(e);
+ }
+ }
+
+ private void addSubmodule(String name, String url, String path,
+ String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
+ Git git) throws GitAPIException, IOException {
+ assert (!repo.isBare());
+ assert (git != null);
+ if (!linkfiles.isEmpty()) {
+ throw new UnsupportedOperationException(
+ JGitText.get().nonBareLinkFilesNotSupported);
+ }
+
+ SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path)
+ .setURI(url);
+ if (monitor != null) {
+ add.setProgressMonitor(monitor);
+ }
+
+ Repository subRepo = add.call();
+ if (revision != null) {
+ try (Git sub = new Git(subRepo)) {
+ sub.checkout().setName(findRef(revision, subRepo)).call();
+ }
+ subRepo.close();
+ git.add().addFilepattern(path).call();
+ }
+ for (CopyFile copyfile : copyfiles) {
+ copyfile.copy();
+ git.add().addFilepattern(copyfile.dest).call();
+ }
+ }
+
+ private static String findRef(String ref, Repository repo)
+ throws IOException {
+ if (!ObjectId.isId(ref)) {
+ Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
+ if (r != null) {
+ return r.getName();
+ }
+ }
+ return ref;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index e0a822479f..6e943e5d36 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -9,11 +9,6 @@
*/
package org.eclipse.jgit.gitrepo;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
-import static org.eclipse.jgit.lib.Constants.R_REMOTES;
-import static org.eclipse.jgit.lib.Constants.R_TAGS;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -31,34 +26,21 @@ import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.GitCommand;
-import org.eclipse.jgit.api.SubmoduleAddCommand;
-import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRefNameException;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.gitrepo.BareSuperprojectWriter.ExtraContent;
import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader;
-import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
-import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
import org.eclipse.jgit.gitrepo.internal.RepoText;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FileUtils;
@@ -80,12 +62,7 @@ import org.eclipse.jgit.util.FileUtils;
* @since 3.4
*/
public class RepoCommand extends GitCommand<RevCommit> {
- private static final int LOCK_FAILURE_MAX_RETRIES = 5;
-
- // Retry exponentially with delays in this range
- private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50;
- private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000;
private String manifestPath;
private String baseUri;
@@ -93,17 +70,18 @@ public class RepoCommand extends GitCommand<RevCommit> {
private String groupsParam;
private String branch;
private String targetBranch = Constants.HEAD;
- private boolean recordRemoteBranch = true;
- private boolean recordSubmoduleLabels = true;
- private boolean recordShallowSubmodules = true;
private PersonIdent author;
private RemoteReader callback;
private InputStream inputStream;
private IncludedFileReader includedReader;
- private boolean ignoreRemoteFailures = false;
+
+ private BareSuperprojectWriter.BareWriterConfig bareWriterConfig = BareSuperprojectWriter.BareWriterConfig
+ .getDefault();
private ProgressMonitor monitor;
+ private final List<ExtraContent> extraContents = new ArrayList<>();
+
/**
* A callback to get ref sha1 of a repository from its uri.
*
@@ -269,14 +247,14 @@ public class RepoCommand extends GitCommand<RevCommit> {
}
@SuppressWarnings("serial")
- private static class ManifestErrorException extends GitAPIException {
+ static class ManifestErrorException extends GitAPIException {
ManifestErrorException(Throwable cause) {
super(RepoText.get().invalidManifest, cause);
}
}
@SuppressWarnings("serial")
- private static class RemoteUnavailableException extends GitAPIException {
+ static class RemoteUnavailableException extends GitAPIException {
RemoteUnavailableException(String uri) {
super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri));
}
@@ -421,7 +399,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
* @since 4.2
*/
public RepoCommand setRecordRemoteBranch(boolean enable) {
- this.recordRemoteBranch = enable;
+ this.bareWriterConfig.recordRemoteBranch = enable;
return this;
}
@@ -436,7 +414,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
* @since 4.4
*/
public RepoCommand setRecordSubmoduleLabels(boolean enable) {
- this.recordSubmoduleLabels = enable;
+ this.bareWriterConfig.recordSubmoduleLabels = enable;
return this;
}
@@ -451,7 +429,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
* @since 4.4
*/
public RepoCommand setRecommendShallow(boolean enable) {
- this.recordShallowSubmodules = enable;
+ this.bareWriterConfig.recordShallowSubmodules = enable;
return this;
}
@@ -485,7 +463,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
* @since 4.3
*/
public RepoCommand setIgnoreRemoteFailures(boolean ignore) {
- this.ignoreRemoteFailures = ignore;
+ this.bareWriterConfig.ignoreRemoteFailures = ignore;
return this;
}
@@ -534,6 +512,22 @@ public class RepoCommand extends GitCommand<RevCommit> {
return this;
}
+ /**
+ * Create a file with the given content in the destination repository
+ *
+ * @param path
+ * where to create the file in the destination repository
+ * @param contents
+ * content for the create file
+ * @return this command
+ *
+ * @since 6.1
+ */
+ public RepoCommand addToDestination(String path, String contents) {
+ this.extraContents.add(new ExtraContent(path, contents));
+ return this;
+ }
+
/** {@inheritDoc} */
@Override
public RevCommit call() throws GitAPIException {
@@ -570,240 +564,18 @@ public class RepoCommand extends GitCommand<RevCommit> {
}
if (repo.isBare()) {
- if (author == null)
- author = new PersonIdent(repo);
- if (callback == null)
- callback = new DefaultRemoteReader();
List<RepoProject> renamedProjects = renameProjects(filteredProjects);
-
- DirCache index = DirCache.newInCore();
- ObjectInserter inserter = repo.newObjectInserter();
-
- try (RevWalk rw = new RevWalk(repo)) {
- prepareIndex(renamedProjects, index, inserter);
- ObjectId treeId = index.writeTree(inserter);
- long prevDelay = 0;
- for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) {
- try {
- return commitTreeOnCurrentTip(
- inserter, rw, treeId);
- } catch (ConcurrentRefUpdateException e) {
- prevDelay = FileUtils.delay(prevDelay,
- LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS,
- LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS);
- Thread.sleep(prevDelay);
- repo.getRefDatabase().refresh();
- }
- }
- // In the last try, just propagate the exceptions
- return commitTreeOnCurrentTip(inserter, rw, treeId);
- } catch (IOException | InterruptedException e) {
- throw new ManifestErrorException(e);
- }
- }
- try (Git git = new Git(repo)) {
- for (RepoProject proj : filteredProjects) {
- addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
- proj.getRevision(), proj.getCopyFiles(),
- proj.getLinkFiles(), git);
- }
- return git.commit().setMessage(RepoText.get().repoCommitMessage)
- .call();
- } catch (IOException e) {
- throw new ManifestErrorException(e);
- }
- }
-
- private void prepareIndex(List<RepoProject> projects, DirCache index,
- ObjectInserter inserter) throws IOException, GitAPIException {
- Config cfg = new Config();
- StringBuilder attributes = new StringBuilder();
- DirCacheBuilder builder = index.builder();
- for (RepoProject proj : projects) {
- String name = proj.getName();
- String path = proj.getPath();
- String url = proj.getUrl();
- ObjectId objectId;
- if (ObjectId.isId(proj.getRevision())) {
- objectId = ObjectId.fromString(proj.getRevision());
- } else {
- objectId = callback.sha1(url, proj.getRevision());
- if (objectId == null && !ignoreRemoteFailures) {
- throw new RemoteUnavailableException(url);
- }
- if (recordRemoteBranch) {
- // "branch" field is only for non-tag references.
- // Keep tags in "ref" field as hint for other tools.
- String field = proj.getRevision().startsWith(R_TAGS) ? "ref" //$NON-NLS-1$
- : "branch"; //$NON-NLS-1$
- cfg.setString("submodule", name, field, //$NON-NLS-1$
- proj.getRevision());
- }
-
- if (recordShallowSubmodules
- && proj.getRecommendShallow() != null) {
- // The shallow recommendation is losing information.
- // As the repo manifests stores the recommended
- // depth in the 'clone-depth' field, while
- // git core only uses a binary 'shallow = true/false'
- // hint, we'll map any depth to 'shallow = true'
- cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
- true);
- }
- }
- if (recordSubmoduleLabels) {
- StringBuilder rec = new StringBuilder();
- rec.append("/"); //$NON-NLS-1$
- rec.append(path);
- for (String group : proj.getGroups()) {
- rec.append(" "); //$NON-NLS-1$
- rec.append(group);
- }
- rec.append("\n"); //$NON-NLS-1$
- attributes.append(rec.toString());
- }
-
- URI submodUrl = URI.create(url);
- if (targetUri != null) {
- submodUrl = relativize(targetUri, submodUrl);
- }
- cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
- cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$
- submodUrl.toString());
-
- // create gitlink
- if (objectId != null) {
- DirCacheEntry dcEntry = new DirCacheEntry(path);
- dcEntry.setObjectId(objectId);
- dcEntry.setFileMode(FileMode.GITLINK);
- builder.add(dcEntry);
-
- for (CopyFile copyfile : proj.getCopyFiles()) {
- RemoteFile rf = callback.readFileWithMode(url,
- proj.getRevision(), copyfile.src);
- objectId = inserter.insert(Constants.OBJ_BLOB,
- rf.getContents());
- dcEntry = new DirCacheEntry(copyfile.dest);
- dcEntry.setObjectId(objectId);
- dcEntry.setFileMode(rf.getFileMode());
- builder.add(dcEntry);
- }
- for (LinkFile linkfile : proj.getLinkFiles()) {
- String link;
- if (linkfile.dest.contains("/")) { //$NON-NLS-1$
- link = FileUtils.relativizeGitPath(
- linkfile.dest.substring(0,
- linkfile.dest.lastIndexOf('/')),
- proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$
- } else {
- link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$
- }
-
- objectId = inserter.insert(Constants.OBJ_BLOB,
- link.getBytes(UTF_8));
- dcEntry = new DirCacheEntry(linkfile.dest);
- dcEntry.setObjectId(objectId);
- dcEntry.setFileMode(FileMode.SYMLINK);
- builder.add(dcEntry);
- }
- }
- }
- String content = cfg.toText();
-
- // create a new DirCacheEntry for .gitmodules file.
- DirCacheEntry dcEntry = new DirCacheEntry(
- Constants.DOT_GIT_MODULES);
- ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
- content.getBytes(UTF_8));
- dcEntry.setObjectId(objectId);
- dcEntry.setFileMode(FileMode.REGULAR_FILE);
- builder.add(dcEntry);
-
- if (recordSubmoduleLabels) {
- // create a new DirCacheEntry for .gitattributes file.
- DirCacheEntry dcEntryAttr = new DirCacheEntry(
- Constants.DOT_GIT_ATTRIBUTES);
- ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
- attributes.toString().getBytes(UTF_8));
- dcEntryAttr.setObjectId(attrId);
- dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
- builder.add(dcEntryAttr);
- }
-
- builder.finish();
- }
-
- private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter,
- RevWalk rw, ObjectId treeId)
- throws IOException, ConcurrentRefUpdateException {
- ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$
- if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) {
- // No change. Do nothing.
- return rw.parseCommit(headId);
+ BareSuperprojectWriter writer = new BareSuperprojectWriter(repo, targetUri,
+ targetBranch,
+ author == null ? new PersonIdent(repo) : author,
+ callback == null ? new DefaultRemoteReader() : callback,
+ bareWriterConfig, extraContents);
+ return writer.write(renamedProjects);
}
- CommitBuilder commit = new CommitBuilder();
- commit.setTreeId(treeId);
- if (headId != null)
- commit.setParentIds(headId);
- commit.setAuthor(author);
- commit.setCommitter(author);
- commit.setMessage(RepoText.get().repoCommitMessage);
-
- ObjectId commitId = inserter.insert(commit);
- inserter.flush();
-
- RefUpdate ru = repo.updateRef(targetBranch);
- ru.setNewObjectId(commitId);
- ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
- Result rc = ru.update(rw);
- switch (rc) {
- case NEW:
- case FORCED:
- case FAST_FORWARD:
- // Successful. Do nothing.
- break;
- case REJECTED:
- case LOCK_FAILURE:
- throw new ConcurrentRefUpdateException(MessageFormat
- .format(JGitText.get().cannotLock, targetBranch),
- ru.getRef(), rc);
- default:
- throw new JGitInternalException(MessageFormat.format(
- JGitText.get().updatingRefFailed,
- targetBranch, commitId.name(), rc));
- }
- return rw.parseCommit(commitId);
- }
-
- private void addSubmodule(String name, String url, String path,
- String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
- Git git) throws GitAPIException, IOException {
- assert (!repo.isBare());
- assert (git != null);
- if (!linkfiles.isEmpty()) {
- throw new UnsupportedOperationException(
- JGitText.get().nonBareLinkFilesNotSupported);
- }
-
- SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path)
- .setURI(url);
- if (monitor != null)
- add.setProgressMonitor(monitor);
-
- Repository subRepo = add.call();
- if (revision != null) {
- try (Git sub = new Git(subRepo)) {
- sub.checkout().setName(findRef(revision, subRepo)).call();
- }
- subRepo.close();
- git.add().addFilepattern(path).call();
- }
- for (CopyFile copyfile : copyfiles) {
- copyfile.copy();
- git.add().addFilepattern(copyfile.dest).call();
- }
+ RegularSuperprojectWriter writer = new RegularSuperprojectWriter(repo, monitor);
+ return writer.write(filteredProjects);
}
/**
@@ -910,13 +682,4 @@ public class RepoCommand extends GitCommand<RevCommit> {
return URI.create(j.toString());
}
- private static String findRef(String ref, Repository repo)
- throws IOException {
- if (!ObjectId.isId(ref)) {
- Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
- if (r != null)
- return r.getName();
- }
- return ref;
- }
}
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 61a2e87671..2adde0a5d0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -387,6 +387,8 @@ public class JGitText extends TranslationBundle {
/***/ public String inMemoryBufferLimitExceeded;
/***/ public String inputDidntMatchLength;
/***/ public String inputStreamMustSupportMark;
+ /***/ public String integerValueNotInRange;
+ /***/ public String integerValueNotInRangeSubSection;
/***/ public String integerValueOutOfRange;
/***/ public String internalRevisionError;
/***/ public String internalServerError;
@@ -398,6 +400,7 @@ public class JGitText extends TranslationBundle {
/***/ public String invalidBooleanValue;
/***/ public String invalidChannel;
/***/ public String invalidCommitParentNumber;
+ /***/ public String invalidCoreAbbrev;
/***/ public String invalidDepth;
/***/ public String invalidEncoding;
/***/ public String invalidEncryption;
@@ -599,6 +602,11 @@ public class JGitText extends TranslationBundle {
/***/ public String pushCertificateInvalidFieldValue;
/***/ public String pushCertificateInvalidHeader;
/***/ public String pushCertificateInvalidSignature;
+ /***/ public String pushDefaultNothing;
+ /***/ public String pushDefaultNoUpstream;
+ /***/ public String pushDefaultSimple;
+ /***/ public String pushDefaultTriangularUpstream;
+ /***/ public String pushDefaultUnknown;
/***/ public String pushIsNotSupportedForBundleTransport;
/***/ public String pushNotPermitted;
/***/ public String pushOptionsNotSupported;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
new file mode 100644
index 0000000000..509515c37a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.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.internal.diffmergetool;
+
+/**
+ * Pre-defined command line diff tools.
+ *
+ * Adds same diff tools as also pre-defined in C-Git
+ * <p>
+ * see "git-core\mergetools\"
+ * </p>
+ * <p>
+ * see links to command line parameter description for the tools
+ * </p>
+ *
+ * <pre>
+ * araxis
+ * bc
+ * bc3
+ * codecompare
+ * deltawalker
+ * diffmerge
+ * diffuse
+ * ecmerge
+ * emerge
+ * examdiff
+ * guiffy
+ * gvimdiff
+ * gvimdiff2
+ * gvimdiff3
+ * kdiff3
+ * kompare
+ * meld
+ * opendiff
+ * p4merge
+ * tkdiff
+ * vimdiff
+ * vimdiff2
+ * vimdiff3
+ * winmerge
+ * xxdiff
+ * </pre>
+ *
+ */
+@SuppressWarnings("nls")
+public enum CommandLineDiffTool {
+ /**
+ * See: <a href=
+ * "https://www.araxis.com/merge/documentation-windows/command-line.en">https://www.araxis.com/merge/documentation-windows/command-line.en</a>
+ */
+ araxis("compare", "-wait -2 \"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://www.scootersoftware.com/v4help/index.html?command_line_reference.html">https://www.scootersoftware.com/v4help/index.html?command_line_reference.html</a>
+ */
+ bc("bcomp", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://www.scootersoftware.com/v4help/index.html?command_line_reference.html">https://www.scootersoftware.com/v4help/index.html?command_line_reference.html</a>
+ */
+ bc3("bcompare", bc),
+ /**
+ * See: <a href=
+ * "https://www.devart.com/codecompare/docs/index.html?comparing_via_command_line.htm">https://www.devart.com/codecompare/docs/index.html?comparing_via_command_line.htm</a>
+ */
+ codecompare("CodeCompare", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://www.deltawalker.com/integrate/command-line">https://www.deltawalker.com/integrate/command-line</a>
+ */
+ deltawalker("DeltaWalker", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html">https://sourcegear.com/diffmerge/webhelp/sec__clargs__diff.html</a>
+ */
+ diffmerge("diffmerge", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://diffuse.sourceforge.net/manual.html#introduction-usage">http://diffuse.sourceforge.net/manual.html#introduction-usage</a>
+ */
+ diffuse("diffuse", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp">http://www.elliecomputing.com/en/OnlineDoc/ecmerge_en/44205167.asp</a>
+ */
+ ecmerge("ecmerge", "--default --mode=diff2 \"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html">https://www.gnu.org/software/emacs/manual/html_node/emacs/Overview-of-Emerge.html</a>
+ */
+ emerge("emacs", "-f emerge-files-command \"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options">https://www.prestosoft.com/ps.asp?page=htmlhelp/edp/command_line_options</a>
+ */
+ examdiff("ExamDiff", "\"$LOCAL\" \"$REMOTE\" -nh"),
+ /**
+ * See: <a href=
+ * "https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html">https://www.guiffy.com/help/GuiffyHelp/GuiffyCmd.html</a>
+ */
+ guiffy("guiffy", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ gvimdiff("gviewdiff", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ gvimdiff2(gvimdiff),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ gvimdiff3(gvimdiff),
+ /**
+ * See: <a href=
+ * "http://kdiff3.sourceforge.net/doc/documentation.html">http://kdiff3.sourceforge.net/doc/documentation.html</a>
+ */
+ kdiff3("kdiff3",
+ "--L1 \"$MERGED (A)\" --L2 \"$MERGED (B)\" \"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://docs.kde.org/trunk5/en/kdesdk/kompare/commandline-options.html">https://docs.kde.org/trunk5/en/kdesdk/kompare/commandline-options.html</a>
+ */
+ kompare("kompare", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "ttp://meldmerge.org/help/file-mode.html">http://meldmerge.org/help/file-mode.html</a>
+ */
+ meld("meld", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://www.manpagez.com/man/1/opendiff/">http://www.manpagez.com/man/1/opendiff/</a>
+ * <p>
+ * Hint: check the ' | cat' for the call
+ * </p>
+ */
+ opendiff("opendiff", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html">https://www.perforce.com/manuals/v15.1/cmdref/p4_merge.html</a>
+ */
+ p4merge("p4merge", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://linux.math.tifr.res.in/manuals/man/tkdiff.html">http://linux.math.tifr.res.in/manuals/man/tkdiff.html</a>
+ */
+ tkdiff("tkdiff", "\"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ vimdiff("viewdiff", gvimdiff),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ vimdiff2(vimdiff),
+ /**
+ * See: <a href=
+ * "http://vimdoc.sourceforge.net/htmldoc/diff.html">http://vimdoc.sourceforge.net/htmldoc/diff.html</a>
+ */
+ vimdiff3(vimdiff),
+ /**
+ * See: <a href=
+ * "http://manual.winmerge.org/Command_line.html">http://manual.winmerge.org/Command_line.html</a>
+ * <p>
+ * Hint: check how 'mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge"'
+ * works
+ * </p>
+ */
+ winmerge("WinMergeU", "-u -e \"$LOCAL\" \"$REMOTE\""),
+ /**
+ * See: <a href=
+ * "http://furius.ca/xxdiff/doc/xxdiff-doc.html">http://furius.ca/xxdiff/doc/xxdiff-doc.html</a>
+ */
+ xxdiff("xxdiff",
+ "-R 'Accel.Search: \"Ctrl+F\"' -R 'Accel.SearchForward: \"Ctrl+G\"' \"$LOCAL\" \"$REMOTE\"");
+
+ CommandLineDiffTool(String path, String parameters) {
+ this.path = path;
+ this.parameters = parameters;
+ }
+
+ CommandLineDiffTool(CommandLineDiffTool from) {
+ this(from.getPath(), from.getParameters());
+ }
+
+ CommandLineDiffTool(String path, CommandLineDiffTool from) {
+ this(path, from.getParameters());
+ }
+
+ private final String path;
+
+ private final String parameters;
+
+ /**
+ * @return path
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * @return parameters as one string
+ */
+ public String getParameters() {
+ return parameters;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
new file mode 100644
index 0000000000..551f634f2d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.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.internal.diffmergetool;
+
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFFTOOL_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CMD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_GUITOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PROMPT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TOOL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_TRUST_EXIT_CODE;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+
+/**
+ * Keeps track of difftool related configuration options.
+ */
+public class DiffToolConfig {
+
+ /** Key for {@link Config#get(SectionParser)}. */
+ public static final Config.SectionParser<DiffToolConfig> KEY = DiffToolConfig::new;
+
+ private final String toolName;
+
+ private final String guiToolName;
+
+ private final boolean prompt;
+
+ private final BooleanTriState trustExitCode;
+
+ private final Map<String, ExternalDiffTool> tools;
+
+ private DiffToolConfig(Config rc) {
+ toolName = rc.getString(CONFIG_DIFF_SECTION, null, CONFIG_KEY_TOOL);
+ guiToolName = rc.getString(CONFIG_DIFF_SECTION, null,
+ CONFIG_KEY_GUITOOL);
+ prompt = rc.getBoolean(CONFIG_DIFFTOOL_SECTION, CONFIG_KEY_PROMPT,
+ true);
+ String trustStr = rc.getString(CONFIG_DIFFTOOL_SECTION, null,
+ CONFIG_KEY_TRUST_EXIT_CODE);
+ if (trustStr != null) {
+ trustExitCode = Boolean.parseBoolean(trustStr)
+ ? BooleanTriState.TRUE
+ : BooleanTriState.FALSE;
+ } else {
+ trustExitCode = BooleanTriState.UNSET;
+ }
+ tools = new HashMap<>();
+ Set<String> subsections = rc.getSubsections(CONFIG_DIFFTOOL_SECTION);
+ for (String name : subsections) {
+ String cmd = rc.getString(CONFIG_DIFFTOOL_SECTION, name,
+ CONFIG_KEY_CMD);
+ String path = rc.getString(CONFIG_DIFFTOOL_SECTION, name,
+ CONFIG_KEY_PATH);
+ if ((cmd != null) || (path != null)) {
+ tools.put(name, new UserDefinedDiffTool(name, path, cmd));
+ }
+ }
+ }
+
+ /**
+ * @return the default diff tool name (diff.tool)
+ */
+ public String getDefaultToolName() {
+ return toolName;
+ }
+
+ /**
+ * @return the default GUI diff tool name (diff.guitool)
+ */
+ public String getDefaultGuiToolName() {
+ return guiToolName;
+ }
+
+ /**
+ * @return the diff tool "prompt" option (difftool.prompt)
+ */
+ public boolean isPrompt() {
+ return prompt;
+ }
+
+ /**
+ * @return the diff tool "trust exit code" option (difftool.trustExitCode)
+ */
+ public boolean isTrustExitCode() {
+ return trustExitCode == BooleanTriState.TRUE;
+ }
+
+ /**
+ * @return the tools map
+ */
+ public Map<String, ExternalDiffTool> getTools() {
+ return tools;
+ }
+
+ /**
+ * @return the tool names
+ */
+ public Set<String> getToolNames() {
+ return tools.keySet();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
new file mode 100644
index 0000000000..39729a4eec
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.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.internal.diffmergetool;
+
+import java.util.TreeMap;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.internal.BooleanTriState;
+
+/**
+ * Manages diff tools.
+ */
+public class DiffTools {
+
+ private final DiffToolConfig config;
+
+ private Map<String, ExternalDiffTool> predefinedTools;
+
+ private Map<String, ExternalDiffTool> userDefinedTools;
+
+ /**
+ * Creates the external diff-tools manager for given repository.
+ *
+ * @param repo
+ * the repository
+ */
+ public DiffTools(Repository repo) {
+ config = repo.getConfig().get(DiffToolConfig.KEY);
+ setupPredefinedTools();
+ setupUserDefinedTools();
+ }
+
+ /**
+ * Compare two versions of a file.
+ *
+ * @param newPath
+ * the new file path
+ * @param oldPath
+ * the old file path
+ * @param newId
+ * the new object ID
+ * @param oldId
+ * the old object ID
+ * @param toolName
+ * the selected tool name (can be null)
+ * @param prompt
+ * the prompt option
+ * @param gui
+ * the GUI option
+ * @param trustExitCode
+ * the "trust exit code" option
+ * @return the return code from executed tool
+ */
+ public int compare(String newPath, String oldPath, String newId,
+ String oldId, String toolName, BooleanTriState prompt,
+ BooleanTriState gui, BooleanTriState trustExitCode) {
+ return 0;
+ }
+
+ /**
+ * @return the tool names
+ */
+ public Set<String> getToolNames() {
+ return config.getToolNames();
+ }
+
+ /**
+ * @return the user defined tools
+ */
+ public Map<String, ExternalDiffTool> getUserDefinedTools() {
+ return Collections.unmodifiableMap(userDefinedTools);
+ }
+
+ /**
+ * @return the available predefined tools
+ */
+ public Map<String, ExternalDiffTool> getAvailableTools() {
+ return Collections.unmodifiableMap(predefinedTools);
+ }
+
+ /**
+ * @return the NOT available predefined tools
+ */
+ public Map<String, ExternalDiffTool> getNotAvailableTools() {
+ return Collections.unmodifiableMap(new TreeMap<>());
+ }
+
+ /**
+ * @param gui
+ * use the diff.guitool setting ?
+ * @return the default tool name
+ */
+ public String getDefaultToolName(BooleanTriState gui) {
+ return gui != BooleanTriState.UNSET ? "my_gui_tool" //$NON-NLS-1$
+ : "my_default_toolname"; //$NON-NLS-1$
+ }
+
+ /**
+ * @return is interactive (config prompt enabled) ?
+ */
+ public boolean isInteractive() {
+ return false;
+ }
+
+ private void setupPredefinedTools() {
+ predefinedTools = new TreeMap<>();
+ for (CommandLineDiffTool tool : CommandLineDiffTool.values()) {
+ predefinedTools.put(tool.name(), new PreDefinedDiffTool(tool));
+ }
+ }
+
+ private void setupUserDefinedTools() {
+ userDefinedTools = new TreeMap<>();
+ Map<String, ExternalDiffTool> userTools = config.getTools();
+ for (String name : userTools.keySet()) {
+ ExternalDiffTool userTool = userTools.get(name);
+ // if difftool.<name>.cmd is defined we have user defined tool
+ if (userTool.getCommand() != null) {
+ userDefinedTools.put(name, userTool);
+ } else if (userTool.getPath() != null) {
+ // if difftool.<name>.path is defined we just overload the path
+ // of predefined tool
+ PreDefinedDiffTool predefTool = (PreDefinedDiffTool) predefinedTools
+ .get(name);
+ if (predefTool != null) {
+ predefTool.setPath(userTool.getPath());
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
new file mode 100644
index 0000000000..f2d7e828cb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.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.internal.diffmergetool;
+
+/**
+ * The external tool interface.
+ */
+public interface ExternalDiffTool {
+
+ /**
+ * @return the tool name
+ */
+ String getName();
+
+ /**
+ * @return the tool path
+ */
+ String getPath();
+
+ /**
+ * @return the tool command
+ */
+ String getCommand();
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
new file mode 100644
index 0000000000..1c69fb4911
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.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.internal.diffmergetool;
+
+/**
+ * The pre-defined diff tool.
+ */
+public class PreDefinedDiffTool extends UserDefinedDiffTool {
+
+ /**
+ * Create a pre-defined diff tool
+ *
+ * @param name
+ * the name
+ * @param path
+ * the path
+ * @param parameters
+ * the tool parameters as one string that is used together with
+ * path as command
+ */
+ public PreDefinedDiffTool(String name, String path, String parameters) {
+ super(name, path, parameters);
+ }
+
+ /**
+ * Creates the pre-defined diff tool
+ *
+ * @param tool
+ * the command line diff tool
+ *
+ */
+ public PreDefinedDiffTool(CommandLineDiffTool tool) {
+ this(tool.name(), tool.getPath(), tool.getParameters());
+ }
+
+ /**
+ * @param path
+ */
+ @Override
+ public void setPath(String path) {
+ // handling of spaces in path
+ if (path.contains(" ")) { //$NON-NLS-1$
+ // add quotes before if needed
+ if (!path.startsWith("\"")) { //$NON-NLS-1$
+ path = "\"" + path; //$NON-NLS-1$
+ }
+ // add quotes after if needed
+ if (!path.endsWith("\"")) { //$NON-NLS-1$
+ path = path + "\""; //$NON-NLS-1$
+ }
+ }
+ super.setPath(path);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return the concatenated path and command of the pre-defined diff tool
+ */
+ @Override
+ public String getCommand() {
+ return getPath() + " " + super.getCommand(); //$NON-NLS-1$
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
new file mode 100644
index 0000000000..012296eb35
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.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.internal.diffmergetool;
+
+/**
+ * The user-defined diff tool.
+ */
+public class UserDefinedDiffTool implements ExternalDiffTool {
+
+ /**
+ * the diff tool name
+ */
+ private final String name;
+
+ /**
+ * the diff tool path
+ */
+ private String path;
+
+ /**
+ * the diff tool command
+ */
+ private final String cmd;
+
+ /**
+ * Creates the diff tool
+ *
+ * @param name
+ * the name
+ * @param path
+ * the path
+ * @param cmd
+ * the command
+ */
+ public UserDefinedDiffTool(String name, String path, String cmd) {
+ this.name = name;
+ this.path = path;
+ this.cmd = cmd;
+ }
+
+ /**
+ * @return the diff tool name
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The path of the diff tool.
+ *
+ * <p>
+ * The path to a pre-defined external diff tool can be overridden by
+ * specifying {@code difftool.<tool>.path} in a configuration file.
+ * </p>
+ * <p>
+ * For a user defined diff tool (that does not override a pre-defined diff
+ * tool), the path is ignored when invoking the tool.
+ * </p>
+ *
+ * @return the diff tool path
+ *
+ * @see <a href=
+ * "https://git-scm.com/docs/git-difftool">https://git-scm.com/docs/git-difftool</a>
+ */
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * The command of the diff tool.
+ *
+ * <p>
+ * A pre-defined external diff tool can be overridden using the tools name
+ * in a configuration file. The overwritten tool is then a user defined tool
+ * and the command of the diff tool is specified with
+ * {@code difftool.<tool>.cmd}. This command must work without prepending
+ * the value of {@link #getPath()} and can sometimes include tool
+ * parameters.
+ * </p>
+ *
+ * @return the diff tool command
+ *
+ * @see <a href=
+ * "https://git-scm.com/docs/git-difftool">https://git-scm.com/docs/git-difftool</a>
+ */
+ @Override
+ public String getCommand() {
+ return cmd;
+ }
+
+ /**
+ * Overrides the path for the given tool. Equivalent to setting
+ * {@code difftool.<tool>.path}.
+ *
+ * @param path
+ * the new diff tool path
+ *
+ * @see <a href=
+ * "https://git-scm.com/docs/git-difftool">https://git-scm.com/docs/git-difftool</a>
+ */
+ public void setPath(String path) {
+ this.path = path;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index 54c527c03c..b30d50921a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -12,6 +12,10 @@
package org.eclipse.jgit.internal.storage.dfs;
import java.io.IOException;
+import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;
@@ -166,6 +170,12 @@ public final class DfsBlockCache {
/** Limits of cache hot count per pack file extension. */
private final int[] cacheHotLimits = new int[PackExt.values().length];
+ /** Consumer of loading and eviction events of indexes. */
+ private final DfsBlockCacheConfig.IndexEventConsumer indexEventConsumer;
+
+ /** Stores timestamps of the last eviction of indexes. */
+ private final Map<EvictKey, Long> indexEvictionMap = new ConcurrentHashMap<>();
+
@SuppressWarnings("unchecked")
private DfsBlockCache(DfsBlockCacheConfig cfg) {
tableSize = tableSize(cfg);
@@ -213,6 +223,7 @@ public final class DfsBlockCache {
cacheHotLimits[i] = DfsBlockCacheConfig.DEFAULT_CACHE_HOT_MAX;
}
}
+ indexEventConsumer = cfg.getIndexEventConsumer();
}
boolean shouldCopyThroughCache(long length) {
@@ -461,6 +472,7 @@ public final class DfsBlockCache {
live -= dead.size;
getStat(liveBytes, dead.key).addAndGet(-dead.size);
getStat(statEvict, dead.key).incrementAndGet();
+ reportIndexEvicted(dead);
} while (maxBytes < live);
clockHand = prev;
}
@@ -515,11 +527,13 @@ public final class DfsBlockCache {
<T> Ref<T> getOrLoadRef(
DfsStreamKey key, long position, RefLoader<T> loader)
throws IOException {
+ long start = System.nanoTime();
int slot = slot(key, position);
HashEntry e1 = table.get(slot);
Ref<T> ref = scanRef(e1, key, position);
if (ref != null) {
getStat(statHit, key).incrementAndGet();
+ reportIndexRequested(ref, true /* cacheHit */, start);
return ref;
}
@@ -532,6 +546,8 @@ public final class DfsBlockCache {
ref = scanRef(e2, key, position);
if (ref != null) {
getStat(statHit, key).incrementAndGet();
+ reportIndexRequested(ref, true /* cacheHit */,
+ start);
return ref;
}
}
@@ -556,6 +572,7 @@ public final class DfsBlockCache {
} finally {
regionLock.unlock();
}
+ reportIndexRequested(ref, false /* cacheHit */, start);
return ref;
}
@@ -682,8 +699,9 @@ public final class DfsBlockCache {
}
private static HashEntry clean(HashEntry top) {
- while (top != null && top.ref.next == null)
+ while (top != null && top.ref.next == null) {
top = top.next;
+ }
if (top == null) {
return null;
}
@@ -691,6 +709,44 @@ public final class DfsBlockCache {
return n == top.next ? top : new HashEntry(n, top.ref);
}
+ private void reportIndexRequested(Ref<?> ref, boolean cacheHit,
+ long start) {
+ if (indexEventConsumer == null
+ || !isIndexOrBitmapExtPos(ref.key.packExtPos)) {
+ return;
+ }
+ EvictKey evictKey = new EvictKey(ref);
+ Long prevEvictedTime = indexEvictionMap.get(evictKey);
+ long now = System.nanoTime();
+ long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
+ : now - prevEvictedTime.longValue();
+ indexEventConsumer.acceptRequestedEvent(ref.key.packExtPos, cacheHit,
+ (now - start) / 1000L /* micros */, ref.size,
+ Duration.ofNanos(sinceLastEvictionNanos));
+ }
+
+ private void reportIndexEvicted(Ref<?> dead) {
+ if (indexEventConsumer == null
+ || !indexEventConsumer.shouldReportEvictedEvent()
+ || !isIndexOrBitmapExtPos(dead.key.packExtPos)) {
+ return;
+ }
+ EvictKey evictKey = new EvictKey(dead);
+ Long prevEvictedTime = indexEvictionMap.get(evictKey);
+ long now = System.nanoTime();
+ long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
+ : now - prevEvictedTime.longValue();
+ indexEvictionMap.put(evictKey, Long.valueOf(now));
+ indexEventConsumer.acceptEvictedEvent(dead.key.packExtPos, dead.size,
+ dead.totalHitCount.get(),
+ Duration.ofNanos(sinceLastEvictionNanos));
+ }
+
+ private static boolean isIndexOrBitmapExtPos(int packExtPos) {
+ return packExtPos == PackExt.INDEX.getPosition()
+ || packExtPos == PackExt.BITMAP_INDEX.getPosition();
+ }
+
private static final class HashEntry {
/** Next entry in the hash table's chain list. */
final HashEntry next;
@@ -712,6 +768,7 @@ public final class DfsBlockCache {
Ref next;
private volatile int hotCount;
+ private AtomicInteger totalHitCount = new AtomicInteger();
Ref(DfsStreamKey key, long position, long size, T v) {
this.key = key;
@@ -736,6 +793,7 @@ public final class DfsBlockCache {
int cap = DfsBlockCache
.getInstance().cacheHotLimits[key.packExtPos];
hotCount = Math.min(cap, hotCount + 1);
+ totalHitCount.incrementAndGet();
}
void markColder() {
@@ -747,6 +805,34 @@ public final class DfsBlockCache {
}
}
+ private static final class EvictKey {
+ private final int keyHash;
+ private final int packExtPos;
+ private final long position;
+
+ EvictKey(Ref<?> ref) {
+ keyHash = ref.key.hash;
+ packExtPos = ref.key.packExtPos;
+ position = ref.position;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof EvictKey) {
+ EvictKey other = (EvictKey) object;
+ return keyHash == other.keyHash
+ && packExtPos == other.packExtPos
+ && position == other.position;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return DfsBlockCache.getInstance().hash(keyHash, position);
+ }
+ }
+
@FunctionalInterface
interface RefLoader<T> {
Ref<T> load() throws IOException;
@@ -763,4 +849,4 @@ public final class DfsBlockCache {
*/
ReadableChannel get() throws IOException;
}
-}
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
index 2716f79a1a..69a37058bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
@@ -18,6 +18,7 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONCURRENCY_LEVEL;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO;
import java.text.MessageFormat;
+import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;
@@ -46,9 +47,10 @@ public class DfsBlockCacheConfig {
private int concurrencyLevel;
private Consumer<Long> refLock;
-
private Map<PackExt, Integer> cacheHotMap;
+ private IndexEventConsumer indexEventConsumer;
+
/**
* Create a default configuration.
*/
@@ -216,6 +218,28 @@ public class DfsBlockCacheConfig {
}
/**
+ * Get the consumer of cache index events.
+ *
+ * @return consumer of cache index events.
+ */
+ public IndexEventConsumer getIndexEventConsumer() {
+ return indexEventConsumer;
+ }
+
+ /**
+ * Set the consumer of cache index events.
+ *
+ * @param indexEventConsumer
+ * consumer of cache index events.
+ * @return {@code this}
+ */
+ public DfsBlockCacheConfig setIndexEventConsumer(
+ IndexEventConsumer indexEventConsumer) {
+ this.indexEventConsumer = indexEventConsumer;
+ return this;
+ }
+
+ /**
* Update properties by setting fields from the configuration.
* <p>
* If a property is not defined in the configuration, then it is left
@@ -272,4 +296,52 @@ public class DfsBlockCacheConfig {
}
return this;
}
-}
+
+ /** Consumer of DfsBlockCache loading and eviction events for indexes. */
+ public interface IndexEventConsumer {
+ /**
+ * Accept an event of an index requested. It could be loaded from either
+ * cache or storage.
+ *
+ * @param packExtPos
+ * position in {@code PackExt} enum
+ * @param cacheHit
+ * true if an index was already in cache. Otherwise, the
+ * index was loaded from storage into the cache in the
+ * current request,
+ * @param loadMicros
+ * time to load an index from cache or storage in
+ * microseconds
+ * @param bytes
+ * number of bytes loaded
+ * @param lastEvictionDuration
+ * time since last eviction, 0 if was not evicted yet
+ */
+ void acceptRequestedEvent(int packExtPos, boolean cacheHit,
+ long loadMicros, long bytes, Duration lastEvictionDuration);
+
+ /**
+ * Accept an event of an index evicted from cache.
+ *
+ * @param packExtPos
+ * position in {@code PackExt} enum
+ * @param bytes
+ * number of bytes evicted
+ * @param totalCacheHitCount
+ * number of times an index was accessed while in cache
+ * @param lastEvictionDuration
+ * time since last eviction, 0 if was not evicted yet
+ */
+ default void acceptEvictedEvent(int packExtPos, long bytes,
+ int totalCacheHitCount, Duration lastEvictionDuration) {
+ // Off by default.
+ }
+
+ /**
+ * @return true if reporting evicted events is enabled.
+ */
+ default boolean shouldReportEvictedEvent() {
+ return false;
+ }
+ }
+} \ No newline at end of file
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 bb76df1d5d..f7a2c94d48 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
@@ -1061,7 +1061,8 @@ public final class DfsPackFile extends BlockBasedFile {
}
in = new BufferedInputStream(in, bs);
bmidx = PackBitmapIndex.read(in, () -> idx(ctx),
- () -> getReverseIdx(ctx));
+ () -> getReverseIdx(ctx),
+ ctx.getOptions().shouldLoadRevIndexInParallel());
} finally {
size = rc.position();
ctx.stats.readBitmapIdxBytes += size;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
index 89de53460c..146f76167d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
@@ -34,6 +34,8 @@ public class DfsReaderOptions {
private int streamPackBufferSize;
+ private boolean loadRevIndexInParallel;
+
/**
* Create a default reader configuration.
*/
@@ -113,6 +115,28 @@ public class DfsReaderOptions {
}
/**
+ * Check if reverse index should be loaded in parallel.
+ *
+ * @return true if reverse index is loaded in parallel for bitmap index.
+ */
+ public boolean shouldLoadRevIndexInParallel() {
+ return loadRevIndexInParallel;
+ }
+
+ /**
+ * Enable (or disable) parallel loading of reverse index.
+ *
+ * @param loadRevIndexInParallel
+ * whether to load reverse index in parallel.
+ * @return {@code this}
+ */
+ public DfsReaderOptions setLoadRevIndexInParallel(
+ boolean loadRevIndexInParallel) {
+ this.loadRevIndexInParallel = loadRevIndexInParallel;
+ return this;
+ }
+
+ /**
* Update properties by setting fields from the configuration.
* <p>
* If a property is not defined in the configuration, then it is left
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 5b6894da9c..99da222395 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -165,6 +165,15 @@ public class InMemoryRepository extends DfsRepository {
}
};
}
+
+ @Override
+ public long getApproximateObjectCount() {
+ long count = 0;
+ for (DfsPackDescription p : packs) {
+ count += p.getObjectCount();
+ }
+ return count;
+ }
}
private static class MemPack extends DfsPackDescription {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
index b0612f9395..cd4f168d86 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
@@ -73,7 +73,6 @@ final class LargePackedWholeObject extends ObjectLoader {
public ObjectStream openStream() throws MissingObjectException, IOException {
PackInputStream packIn;
// ctx is closed by PackInputStream, or explicitly in the finally block
- @SuppressWarnings("resource")
DfsReader ctx = db.newReader();
try {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
index 7dedeb57ab..094fdc1559 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
@@ -263,4 +263,17 @@ class CachedObjectDirectory extends FileObjectDatabase {
private AlternateHandle.Id getAlternateId() {
return wrapped.getAlternateId();
}
+
+ @Override
+ public long getApproximateObjectCount() {
+ long count = 0;
+ for (Pack p : getPacks()) {
+ try {
+ count += p.getObjectCount();
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+ return count;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
index f02c8613a0..5152367d23 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
@@ -40,6 +40,7 @@ import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
/**
* A mutable stack of reftables on local filesystem storage. Not thread-safe.
@@ -527,11 +528,19 @@ public class FileReftableStack implements AutoCloseable {
return false;
}
+ reload();
for (File f : deleteOnSuccess) {
- Files.delete(f.toPath());
+ try {
+ Files.delete(f.toPath());
+ } catch (IOException e) {
+ // Ignore: this can happen on Windows in case of concurrent processes.
+ // leave the garbage and continue.
+ if (!SystemReader.getInstance().isWindows()) {
+ throw e;
+ }
+ }
}
- reload();
return true;
} finally {
if (tmpTable != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index b37155717c..3e92cddacd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -672,18 +672,20 @@ public class FileRepository extends Repository {
if (writeLogs) {
List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
- .getReverseEntries();
+ .getReverseEntries();
Collections.reverse(logs);
for (ReflogEntry e : logs) {
logWriter.log(r.getName(), e);
}
- }
+ }
}
try (RevWalk rw = new RevWalk(this)) {
bru.execute(rw, NullProgressMonitor.INSTANCE);
}
+ oldDb.close();
+
List<String> failed = new ArrayList<>();
for (ReceiveCommand cmd : bru.getCommands()) {
if (cmd.getResult() != ReceiveCommand.Result.OK) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
index 33621a1e9f..b9af83d24d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
@@ -14,6 +14,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
@@ -24,6 +25,8 @@ import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObjectResult;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -52,15 +55,22 @@ class LooseObjects {
private final UnpackedObjectCache unpackedObjectCache;
+ private final boolean trustFolderStat;
+
/**
* Initialize a reference to an on-disk object directory.
*
+ * @param config
+ * configuration for the loose objects handler.
* @param dir
* the location of the <code>objects</code> directory.
*/
- LooseObjects(File dir) {
+ LooseObjects(Config config, File dir) {
directory = dir;
unpackedObjectCache = new UnpackedObjectCache();
+ trustFolderStat = config.getBoolean(
+ ConfigConstants.CONFIG_CORE_SECTION,
+ ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
}
/**
@@ -98,6 +108,19 @@ class LooseObjects {
* @return {@code true} if the specified object is stored as a loose object.
*/
boolean has(AnyObjectId objectId) {
+ boolean exists = hasWithoutRefresh(objectId);
+ if (trustFolderStat || exists) {
+ return exists;
+ }
+ try (InputStream stream = Files.newInputStream(directory.toPath())) {
+ // refresh directory to work around NFS caching issue
+ } catch (IOException e) {
+ return false;
+ }
+ return hasWithoutRefresh(objectId);
+ }
+
+ private boolean hasWithoutRefresh(AnyObjectId objectId) {
return fileFor(objectId).exists();
}
@@ -183,6 +206,22 @@ class LooseObjects {
*/
ObjectLoader getObjectLoader(WindowCursor curs, File path, AnyObjectId id)
throws IOException {
+ try {
+ return getObjectLoaderWithoutRefresh(curs, path, id);
+ } catch (FileNotFoundException e) {
+ if (trustFolderStat) {
+ throw e;
+ }
+ try (InputStream stream = Files
+ .newInputStream(directory.toPath())) {
+ // refresh directory to work around NFS caching issues
+ }
+ return getObjectLoaderWithoutRefresh(curs, path, id);
+ }
+ }
+
+ private ObjectLoader getObjectLoaderWithoutRefresh(WindowCursor curs,
+ File path, AnyObjectId id) throws IOException {
try (FileInputStream in = new FileInputStream(path)) {
unpackedObjectCache().add(id);
return UnpackedObject.open(in, path, id, curs);
@@ -203,16 +242,34 @@ class LooseObjects {
}
long getSize(WindowCursor curs, AnyObjectId id) throws IOException {
+ try {
+ return getSizeWithoutRefresh(curs, id);
+ } catch (FileNotFoundException noFile) {
+ try {
+ if (trustFolderStat) {
+ throw noFile;
+ }
+ try (InputStream stream = Files
+ .newInputStream(directory.toPath())) {
+ // refresh directory to work around NFS caching issue
+ }
+ return getSizeWithoutRefresh(curs, id);
+ } catch (FileNotFoundException e) {
+ if (fileFor(id).exists()) {
+ throw noFile;
+ }
+ unpackedObjectCache().remove(id);
+ return -1;
+ }
+ }
+ }
+
+ private long getSizeWithoutRefresh(WindowCursor curs, AnyObjectId id)
+ throws IOException {
File f = fileFor(id);
try (FileInputStream in = new FileInputStream(f)) {
unpackedObjectCache().add(id);
return UnpackedObject.getSize(in, id, curs);
- } catch (FileNotFoundException noFile) {
- if (f.exists()) {
- throw noFile;
- }
- unpackedObjectCache().remove(id);
- return -1;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index 627facca02..531fd78cd0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -120,7 +120,7 @@ public class ObjectDirectory extends FileObjectDatabase {
File packDirectory = new File(objects, "pack"); //$NON-NLS-1$
File preservedDirectory = new File(packDirectory, "preserved"); //$NON-NLS-1$
alternatesFile = new File(objects, Constants.INFO_ALTERNATES);
- loose = new LooseObjects(objects);
+ loose = new LooseObjects(config, objects);
packed = new PackDirectory(config, packDirectory);
preserved = new PackDirectory(config, preservedDirectory);
this.fs = fs;
@@ -212,6 +212,20 @@ public class ObjectDirectory extends FileObjectDatabase {
return packed.getPacks();
}
+ /** {@inheritDoc} */
+ @Override
+ public long getApproximateObjectCount() {
+ long count = 0;
+ for (Pack p : getPacks()) {
+ try {
+ count += p.getIndex().getObjectCount();
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+ return count;
+ }
+
/**
* {@inheritDoc}
* <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
index 8401f0718a..8fb17fcf21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
@@ -95,7 +95,7 @@ public abstract class PackBitmapIndex {
*/
public static PackBitmapIndex read(InputStream fd, PackIndex packIndex,
PackReverseIndex reverseIndex) throws IOException {
- return new PackBitmapIndexV1(fd, () -> packIndex, () -> reverseIndex);
+ return new PackBitmapIndexV1(fd, packIndex, reverseIndex);
}
/**
@@ -114,6 +114,8 @@ public abstract class PackBitmapIndex {
* @param reverseIndexSupplier
* the supplier for pack reverse index for the corresponding pack
* file.
+ * @param loadParallelRevIndex
+ * whether reverse index should be loaded in parallel
* @return a copy of the index in-memory.
* @throws java.io.IOException
* the stream cannot be read.
@@ -122,10 +124,11 @@ public abstract class PackBitmapIndex {
*/
public static PackBitmapIndex read(InputStream fd,
SupplierWithIOException<PackIndex> packIndexSupplier,
- SupplierWithIOException<PackReverseIndex> reverseIndexSupplier)
+ SupplierWithIOException<PackReverseIndex> reverseIndexSupplier,
+ boolean loadParallelRevIndex)
throws IOException {
return new PackBitmapIndexV1(fd, packIndexSupplier,
- reverseIndexSupplier);
+ reverseIndexSupplier, loadParallelRevIndex);
}
/** Footer checksum applied on the bottom of the pack file. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
index 6846e3bcad..21aba3e6a3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
@@ -17,6 +17,12 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.JGitText;
@@ -40,6 +46,23 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
private static final int MAX_XOR_OFFSET = 126;
+ private static final ExecutorService executor = Executors
+ .newCachedThreadPool(new ThreadFactory() {
+ private final ThreadFactory baseFactory = Executors
+ .defaultThreadFactory();
+
+ private final AtomicInteger threadNumber = new AtomicInteger(0);
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread thread = baseFactory.newThread(runnable);
+ thread.setName("JGit-PackBitmapIndexV1-" //$NON-NLS-1$
+ + threadNumber.getAndIncrement());
+ thread.setDaemon(true);
+ return thread;
+ }
+ });
+
private final PackIndex packIndex;
private final PackReverseIndex reverseIndex;
private final EWAHCompressedBitmap commits;
@@ -49,15 +72,28 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
private final ObjectIdOwnerMap<StoredBitmap> bitmaps;
+ PackBitmapIndexV1(final InputStream fd, PackIndex packIndex,
+ PackReverseIndex reverseIndex) throws IOException {
+ this(fd, () -> packIndex, () -> reverseIndex, false);
+ }
+
PackBitmapIndexV1(final InputStream fd,
SupplierWithIOException<PackIndex> packIndexSupplier,
- SupplierWithIOException<PackReverseIndex> reverseIndexSupplier)
+ SupplierWithIOException<PackReverseIndex> reverseIndexSupplier,
+ boolean loadParallelRevIndex)
throws IOException {
// An entry is object id, xor offset, flag byte, and a length encoded
// bitmap. The object id is an int32 of the nth position sorted by name.
super(new ObjectIdOwnerMap<StoredBitmap>());
this.bitmaps = getBitmaps();
+ // Optionally start loading reverse index in parallel to loading bitmap
+ // from storage.
+ Future<PackReverseIndex> reverseIndexFuture = null;
+ if (loadParallelRevIndex) {
+ reverseIndexFuture = executor.submit(reverseIndexSupplier::get);
+ }
+
final byte[] scratch = new byte[32];
IO.readFully(fd, scratch, 0, scratch.length);
@@ -164,7 +200,18 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
bitmaps.add(sb);
}
- this.reverseIndex = reverseIndexSupplier.get();
+ PackReverseIndex computedReverseIndex;
+ if (loadParallelRevIndex && reverseIndexFuture != null) {
+ try {
+ computedReverseIndex = reverseIndexFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ // Fallback to loading reverse index through a supplier.
+ computedReverseIndex = reverseIndexSupplier.get();
+ }
+ } else {
+ computedReverseIndex = reverseIndexSupplier.get();
+ }
+ this.reverseIndex = computedReverseIndex;
}
/** {@inheritDoc} */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
index f32909f44d..a7f28c6778 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
@@ -63,12 +63,12 @@ class PackDirectory {
private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY,
new Pack[0]);
- private final Config config;
-
private final File directory;
private final AtomicReference<PackList> packList;
+ private final boolean trustFolderStat;
+
/**
* Initialize a reference to an on-disk 'pack' directory.
*
@@ -78,9 +78,16 @@ class PackDirectory {
* the location of the {@code pack} directory.
*/
PackDirectory(Config config, File directory) {
- this.config = config;
this.directory = directory;
packList = new AtomicReference<>(NO_PACKS);
+
+ // Whether to trust the pack folder's modification time. If set to false
+ // we will always scan the .git/objects/pack folder to check for new
+ // pack files. If set to true (default) we use the folder's size,
+ // modification time, and key (inode) and assume that no new pack files
+ // can be in this folder if these attributes have not changed.
+ trustFolderStat = config.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
}
/**
@@ -331,16 +338,6 @@ class PackDirectory {
}
boolean searchPacksAgain(PackList old) {
- // Whether to trust the pack folder's modification time. If set
- // to false we will always scan the .git/objects/pack folder to
- // check for new pack files. If set to true (default) we use the
- // lastmodified attribute of the folder and assume that no new
- // pack files can be in this folder if his modification time has
- // not changed.
- boolean trustFolderStat = config.getBoolean(
- ConfigConstants.CONFIG_CORE_SECTION,
- ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
-
return ((!trustFolderStat) || old.snapshot.isModified(directory))
&& old != scanPacks(old);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index b46ffe3670..c7322b17bb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -30,10 +30,12 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.DigestInputStream;
import java.security.MessageDigest;
@@ -60,6 +62,7 @@ import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.TrustPackedRefsStat;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
@@ -177,6 +180,10 @@ public class RefDirectory extends RefDatabase {
private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
+ private final boolean trustFolderStat;
+
+ private final TrustPackedRefsStat trustPackedRefsStat;
+
RefDirectory(FileRepository db) {
final FS fs = db.getFS();
parent = db;
@@ -188,6 +195,13 @@ public class RefDirectory extends RefDatabase {
looseRefs.set(RefList.<LooseRef> emptyList());
packedRefs.set(NO_PACKED_REFS);
+ trustFolderStat = db.getConfig()
+ .getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+ trustPackedRefsStat = db.getConfig()
+ .getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT,
+ TrustPackedRefsStat.UNSET);
}
Repository getRepository() {
@@ -889,13 +903,30 @@ public class RefDirectory extends RefDatabase {
}
PackedRefList getPackedRefs() throws IOException {
- boolean trustFolderStat = getRepository().getConfig().getBoolean(
- ConfigConstants.CONFIG_CORE_SECTION,
- ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
-
final PackedRefList curList = packedRefs.get();
- if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
- return curList;
+
+ switch (trustPackedRefsStat) {
+ case NEVER:
+ break;
+ case AFTER_OPEN:
+ try (InputStream stream = Files
+ .newInputStream(packedRefsFile.toPath())) {
+ // open the file to refresh attributes (on some NFS clients)
+ } catch (FileNotFoundException | NoSuchFileException e) {
+ // Ignore as packed-refs may not exist
+ }
+ //$FALL-THROUGH$
+ case ALWAYS:
+ if (!curList.snapshot.isModified(packedRefsFile)) {
+ return curList;
+ }
+ break;
+ case UNSET:
+ if (trustFolderStat
+ && !curList.snapshot.isModified(packedRefsFile)) {
+ return curList;
+ }
+ break;
}
final PackedRefList newList = readPackedRefs();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java
new file mode 100644
index 0000000000..ca2095feec
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022, Tencent.
+ *
+ * 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.internal.storage.io;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+
+/**
+ * An OutputStream that keeps a digest and checks every N bytes for
+ * cancellation.
+ */
+public class CancellableDigestOutputStream extends OutputStream {
+
+ /** The OutputStream checks every this value for cancellation **/
+ public static final int BYTES_TO_WRITE_BEFORE_CANCEL_CHECK = 128 * 1024;
+
+ private final ProgressMonitor writeMonitor;
+
+ private final OutputStream out;
+
+ private final MessageDigest md = Constants.newMessageDigest();
+
+ private long count;
+
+ private long checkCancelAt;
+
+ /**
+ * Initialize a CancellableDigestOutputStream.
+ *
+ * @param writeMonitor
+ * monitor to update on output progress and check cancel.
+ * @param out
+ * target stream to receive all contents.
+ */
+ public CancellableDigestOutputStream(ProgressMonitor writeMonitor,
+ OutputStream out) {
+ this.writeMonitor = writeMonitor;
+ this.out = out;
+ this.checkCancelAt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
+ }
+
+ /**
+ * Get the monitor which is used to update on output progress and check
+ * cancel.
+ *
+ * @return the monitor
+ */
+ public final ProgressMonitor getWriteMonitor() {
+ return writeMonitor;
+ }
+
+ /**
+ * Obtain the current SHA-1 digest.
+ *
+ * @return SHA-1 digest
+ */
+ public final byte[] getDigest() {
+ return md.digest();
+ }
+
+ /**
+ * Get total number of bytes written since stream start.
+ *
+ * @return total number of bytes written since stream start.
+ */
+ public final long length() {
+ return count;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final void write(int b) throws IOException {
+ if (checkCancelAt <= count) {
+ if (writeMonitor.isCancelled()) {
+ throw new InterruptedIOException();
+ }
+ checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
+ }
+
+ out.write(b);
+ md.update((byte) b);
+ count++;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final void write(byte[] b, int off, int len) throws IOException {
+ while (0 < len) {
+ if (checkCancelAt <= count) {
+ if (writeMonitor.isCancelled()) {
+ throw new InterruptedIOException();
+ }
+ checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
+ }
+
+ int n = Math.min(len, BYTES_TO_WRITE_BEFORE_CANCEL_CHECK);
+ out.write(b, off, n);
+ md.update(b, off, n);
+ count += n;
+
+ off += n;
+ len -= n;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void flush() throws IOException {
+ out.flush();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
index 7104b9453e..2d0fe28dae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
@@ -17,10 +17,8 @@ import static org.eclipse.jgit.lib.Constants.PACK_SIGNATURE;
import java.io.IOException;
import java.io.OutputStream;
-import java.security.MessageDigest;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.util.NB;
@@ -28,25 +26,14 @@ import org.eclipse.jgit.util.NB;
* Custom output stream to support
* {@link org.eclipse.jgit.internal.storage.pack.PackWriter}.
*/
-public final class PackOutputStream extends OutputStream {
- private static final int BYTES_TO_WRITE_BEFORE_CANCEL_CHECK = 128 * 1024;
-
- private final ProgressMonitor writeMonitor;
-
- private final OutputStream out;
+public final class PackOutputStream extends CancellableDigestOutputStream {
private final PackWriter packWriter;
- private final MessageDigest md = Constants.newMessageDigest();
-
- private long count;
-
private final byte[] headerBuffer = new byte[32];
private final byte[] copyBuffer = new byte[64 << 10];
- private long checkCancelAt;
-
private boolean ofsDelta;
/**
@@ -66,48 +53,8 @@ public final class PackOutputStream extends OutputStream {
*/
public PackOutputStream(final ProgressMonitor writeMonitor,
final OutputStream out, final PackWriter pw) {
- this.writeMonitor = writeMonitor;
- this.out = out;
+ super(writeMonitor, out);
this.packWriter = pw;
- this.checkCancelAt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
- }
-
- /** {@inheritDoc} */
- @Override
- public final void write(int b) throws IOException {
- count++;
- out.write(b);
- md.update((byte) b);
- }
-
- /** {@inheritDoc} */
- @Override
- public final void write(byte[] b, int off, int len)
- throws IOException {
- while (0 < len) {
- final int n = Math.min(len, BYTES_TO_WRITE_BEFORE_CANCEL_CHECK);
- count += n;
-
- if (checkCancelAt <= count) {
- if (writeMonitor.isCancelled()) {
- throw new IOException(
- JGitText.get().packingCancelledDuringObjectsWriting);
- }
- checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
- }
-
- out.write(b, off, n);
- md.update(b, off, n);
-
- off += n;
- len -= n;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public void flush() throws IOException {
- out.flush();
}
final void writeFileHeader(int version, long objectCount)
@@ -160,7 +107,7 @@ public final class PackOutputStream extends OutputStream {
ObjectToPack b = otp.getDeltaBase();
if (b != null && (b.isWritten() & ofsDelta)) { // Non-short-circuit logic is intentional
int n = objectHeader(rawLength, OBJ_OFS_DELTA, headerBuffer);
- n = ofsDelta(count - b.getOffset(), headerBuffer, n);
+ n = ofsDelta(length() - b.getOffset(), headerBuffer, n);
write(headerBuffer, 0, n);
} else if (otp.isDeltaRepresentation()) {
int n = objectHeader(rawLength, OBJ_REF_DELTA, headerBuffer);
@@ -209,20 +156,6 @@ public final class PackOutputStream extends OutputStream {
}
void endObject() {
- writeMonitor.update(1);
- }
-
- /**
- * Get total number of bytes written since stream start.
- *
- * @return total number of bytes written since stream start.
- */
- public final long length() {
- return count;
- }
-
- /** @return obtain the current SHA-1 digest. */
- final byte[] getDigest() {
- return md.digest();
+ getWriteMonitor().update(1);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
index 648d4a1821..659ccb8c55 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -502,6 +502,98 @@ public class OpenSshConfigFile implements SshConfigStore {
}
/**
+ * Converts an OpenSSH time value into a number of seconds. The format is
+ * defined by OpenSSH as a sequence of (positive) integers with suffixes for
+ * seconds, minutes, hours, days, and weeks.
+ *
+ * @param value
+ * to convert
+ * @return the parsed value as a number of seconds, or -1 if the value is
+ * not a valid OpenSSH time value
+ * @see <a href="https://man.openbsd.org/sshd_config.5#TIME_FORMATS">OpenBSD
+ * man 5 sshd_config, section TIME FORMATS</a>
+ */
+ public static int timeSpec(String value) {
+ if (value == null) {
+ return -1;
+ }
+ try {
+ int length = value.length();
+ int i = 0;
+ int seconds = 0;
+ boolean valueSeen = false;
+ while (i < length) {
+ // Skip whitespace
+ char ch = value.charAt(i);
+ if (Character.isWhitespace(ch)) {
+ i++;
+ continue;
+ }
+ if (ch == '+') {
+ // OpenSSH uses strtol with base 10: a leading plus sign is
+ // allowed.
+ i++;
+ }
+ int val = 0;
+ int j = i;
+ while (j < length) {
+ ch = value.charAt(j++);
+ if (ch >= '0' && ch <= '9') {
+ val = Math.addExact(Math.multiplyExact(val, 10),
+ ch - '0');
+ } else {
+ j--;
+ break;
+ }
+ }
+ if (i == j) {
+ // No digits seen
+ return -1;
+ }
+ i = j;
+ int multiplier = 1;
+ if (i < length) {
+ ch = value.charAt(i++);
+ switch (ch) {
+ case 's':
+ case 'S':
+ break;
+ case 'm':
+ case 'M':
+ multiplier = 60;
+ break;
+ case 'h':
+ case 'H':
+ multiplier = 3600;
+ break;
+ case 'd':
+ case 'D':
+ multiplier = 24 * 3600;
+ break;
+ case 'w':
+ case 'W':
+ multiplier = 7 * 24 * 3600;
+ break;
+ default:
+ if (Character.isWhitespace(ch)) {
+ break;
+ }
+ // Invalid time spec
+ return -1;
+ }
+ }
+ seconds = Math.addExact(seconds,
+ Math.multiplyExact(val, multiplier));
+ valueSeen = true;
+ }
+ return valueSeen ? seconds : -1;
+ } catch (ArithmeticException e) {
+ // Overflow
+ return -1;
+ }
+ }
+
+ /**
* Retrieves the local user name as given in the constructor.
*
* @return the user name
@@ -549,6 +641,7 @@ public class OpenSshConfigFile implements SshConfigStore {
LIST_KEYS.add(SshConstants.GLOBAL_KNOWN_HOSTS_FILE);
LIST_KEYS.add(SshConstants.SEND_ENV);
LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE);
+ LIST_KEYS.add(SshConstants.ADD_KEYS_TO_AGENT); // confirm timeSpec
}
/**
@@ -871,7 +964,8 @@ public class OpenSshConfigFile implements SshConfigStore {
if (options != null) {
// HOSTNAME already done above
String value = options.get(SshConstants.IDENTITY_AGENT);
- if (value != null) {
+ if (value != null && !SshConstants.NONE.equals(value)
+ && !SshConstants.ENV_SSH_AUTH_SOCKET.equals(value)) {
value = r.substitute(value, Replacer.DEFAULT_TOKENS, true);
value = toFile(value, home).getPath();
options.put(SshConstants.IDENTITY_AGENT, value);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java
new file mode 100644
index 0000000000..9109cfd769
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> 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 static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.api.errors.InvalidConfigurationException;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Git configuration option <a
+ * href=https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev">
+ * core.abbrev</a>
+ *
+ * @since 6.1
+ */
+public final class AbbrevConfig {
+ private static final String VALUE_NO = "no"; //$NON-NLS-1$
+
+ private static final String VALUE_AUTO = "auto"; //$NON-NLS-1$
+
+ /**
+ * The minimum value of abbrev
+ */
+ public static final int MIN_ABBREV = 4;
+
+ /**
+ * Cap configured core.abbrev to range between minimum of 4 and number of
+ * hex-digits of a full object id.
+ *
+ * @param len
+ * configured number of hex-digits to abbreviate object ids to
+ * @return core.abbrev capped to range between minimum of 4 and number of
+ * hex-digits of a full object id
+ */
+ public static int capAbbrev(int len) {
+ return Math.min(Math.max(MIN_ABBREV, len),
+ Constants.OBJECT_ID_STRING_LENGTH);
+ }
+
+ /**
+ * No abbreviation
+ */
+ public final static AbbrevConfig NO = new AbbrevConfig(
+ Constants.OBJECT_ID_STRING_LENGTH);
+
+ /**
+ * Parse string value of core.abbrev git option for a given repository
+ *
+ * @param repo
+ * repository
+ * @return the parsed AbbrevConfig
+ * @throws InvalidConfigurationException
+ * if value of core.abbrev is invalid
+ */
+ public static AbbrevConfig parseFromConfig(Repository repo)
+ throws InvalidConfigurationException {
+ Config config = repo.getConfig();
+ String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_ABBREV);
+ if (value == null || value.equalsIgnoreCase(VALUE_AUTO)) {
+ return auto(repo);
+ }
+ if (value.equalsIgnoreCase(VALUE_NO)) {
+ return NO;
+ }
+ try {
+ int len = config.getIntInRange(ConfigConstants.CONFIG_CORE_SECTION,
+ ConfigConstants.CONFIG_KEY_ABBREV, MIN_ABBREV,
+ Constants.OBJECT_ID_STRING_LENGTH, UNSET_INT);
+ if (len == UNSET_INT) {
+ // Unset was checked above. If we get UNSET_INT here, then
+ // either the value was UNSET_INT, or it was an invalid value
+ // (not an integer, or out of range), and EGit's
+ // ReportingTypedGetter caught the exception and has logged a
+ // warning. In either case we should fall back to some sane
+ // default.
+ len = OBJECT_ID_ABBREV_STRING_LENGTH;
+ }
+ return new AbbrevConfig(len);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidConfigurationException(MessageFormat
+ .format(JGitText.get().invalidCoreAbbrev, value), e);
+ }
+ }
+
+ /**
+ * An appropriate value is computed based on the approximate number of
+ * packed objects in a repository, which hopefully is enough for abbreviated
+ * object names to stay unique for some time.
+ *
+ * @param repo
+ * @return appropriate value computed based on the approximate number of
+ * packed objects in a repository
+ */
+ private static AbbrevConfig auto(Repository repo) {
+ long count = repo.getObjectDatabase().getApproximateObjectCount();
+ if (count == -1) {
+ return new AbbrevConfig(OBJECT_ID_ABBREV_STRING_LENGTH);
+ }
+ // find msb, round to next power of 2
+ int len = 63 - Long.numberOfLeadingZeros(count) + 1;
+ // With the order of 2^len objects, we expect a collision at
+ // 2^(len/2). But we also care about hex chars, not bits, and
+ // there are 4 bits per hex. So all together we need to divide
+ // by 2; but we also want to round odd numbers up, hence adding
+ // one before dividing.
+ len = (len + 1) / 2;
+ // for small repos use at least fallback length
+ return new AbbrevConfig(Math.max(len, OBJECT_ID_ABBREV_STRING_LENGTH));
+ }
+
+ /**
+ * All other possible abbreviation lengths. Valid range 4 to number of
+ * hex-digits of an unabbreviated object id (40 for SHA1 object ids, jgit
+ * doesn't support SHA256 yet).
+ */
+ private int abbrev;
+
+ /**
+ * @param abbrev
+ */
+ private AbbrevConfig(int abbrev) {
+ this.abbrev = capAbbrev(abbrev);
+ }
+
+ /**
+ * Get the configured abbreviation length for object ids.
+ *
+ * @return the configured abbreviation length for object ids
+ */
+ public int get() {
+ return abbrev;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(abbrev);
+ }
+} \ No newline at end of file
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 6da6f1204a..aa613d07eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
@@ -138,6 +138,18 @@ public class BranchConfig {
}
/**
+ * Get the remote this branch is configured to push to.
+ *
+ * @return the remote this branch is configured to push to, or {@code null}
+ * if not defined
+ * @since 6.1
+ */
+ public String getPushRemote() {
+ return config.getString(ConfigConstants.CONFIG_BRANCH_SECTION,
+ branchName, ConfigConstants.CONFIG_KEY_PUSH_REMOTE);
+ }
+
+ /**
* Get the name of the upstream branch as it is called on the remote
*
* @return the name of the upstream branch as it is called on the remote, or
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 22e1f98181..55cc02683a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
@@ -18,11 +18,13 @@ import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.text.MessageFormat;
+import java.util.Locale;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Config.ConfigEnum;
import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
@@ -34,22 +36,76 @@ import org.eclipse.jgit.util.RawParseUtils;
* @since 5.13
*/
public class CommitConfig {
+
/**
* Key for {@link Config#get(SectionParser)}.
*/
public static final Config.SectionParser<CommitConfig> KEY = CommitConfig::new;
+ private static final String CUT = " ------------------------ >8 ------------------------\n"; //$NON-NLS-1$
+
+ /**
+ * How to clean up commit messages when committing.
+ *
+ * @since 6.1
+ */
+ public enum CleanupMode implements ConfigEnum {
+
+ /**
+ * {@link #WHITESPACE}, additionally remove comment lines.
+ */
+ STRIP,
+
+ /**
+ * Remove trailing whitespace and leading and trailing empty lines;
+ * collapse multiple empty lines to a single one.
+ */
+ WHITESPACE,
+
+ /**
+ * Make no changes.
+ */
+ VERBATIM,
+
+ /**
+ * Omit everything from the first "scissor" line on, then apply
+ * {@link #WHITESPACE}.
+ */
+ SCISSORS,
+
+ /**
+ * Use {@link #STRIP} for user-edited messages, otherwise
+ * {@link #WHITESPACE}, unless overridden by a git config setting other
+ * than DEFAULT.
+ */
+ DEFAULT;
+
+ @Override
+ public String toConfigValue() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+
+ @Override
+ public boolean matchConfigValue(String in) {
+ return toConfigValue().equals(in);
+ }
+ }
+
private final static Charset DEFAULT_COMMIT_MESSAGE_ENCODING = StandardCharsets.UTF_8;
private String i18nCommitEncoding;
private String commitTemplatePath;
+ private CleanupMode cleanupMode;
+
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);
+ cleanupMode = rc.getEnum(ConfigConstants.CONFIG_COMMIT_SECTION, null,
+ ConfigConstants.CONFIG_KEY_CLEANUP, CleanupMode.DEFAULT);
}
/**
@@ -75,6 +131,48 @@ public class CommitConfig {
}
/**
+ * Retrieves the {@link CleanupMode} as given by git config
+ * {@code commit.cleanup}.
+ *
+ * @return the {@link CleanupMode}; {@link CleanupMode#DEFAULT} if the git
+ * config is not set
+ * @since 6.1
+ */
+ @NonNull
+ public CleanupMode getCleanupMode() {
+ return cleanupMode;
+ }
+
+ /**
+ * Computes a non-default {@link CleanupMode} from the given mode and the
+ * git config.
+ *
+ * @param mode
+ * {@link CleanupMode} to resolve
+ * @param defaultStrip
+ * if {@code true} return {@link CleanupMode#STRIP} if the git
+ * config is also "default", otherwise return
+ * {@link CleanupMode#WHITESPACE}
+ * @return the {@code mode}, if it is not {@link CleanupMode#DEFAULT},
+ * otherwise the resolved mode, which is never
+ * {@link CleanupMode#DEFAULT}
+ * @since 6.1
+ */
+ @NonNull
+ public CleanupMode resolve(@NonNull CleanupMode mode,
+ boolean defaultStrip) {
+ if (CleanupMode.DEFAULT == mode) {
+ CleanupMode defaultMode = getCleanupMode();
+ if (CleanupMode.DEFAULT == defaultMode) {
+ return defaultStrip ? CleanupMode.STRIP
+ : CleanupMode.WHITESPACE;
+ }
+ return defaultMode;
+ }
+ return mode;
+ }
+
+ /**
* 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.
@@ -135,4 +233,86 @@ public class CommitConfig {
return commitMessageEncoding;
}
+
+ /**
+ * Processes a text according to the given {@link CleanupMode}.
+ *
+ * @param text
+ * text to process
+ * @param mode
+ * {@link CleanupMode} to use
+ * @param commentChar
+ * comment character (normally {@code #}) to use if {@code mode}
+ * is {@link CleanupMode#STRIP} or {@link CleanupMode#SCISSORS}
+ * @return the processed text
+ * @throws IllegalArgumentException
+ * if {@code mode} is {@link CleanupMode#DEFAULT} (use
+ * {@link #resolve(CleanupMode, boolean)} first)
+ * @since 6.1
+ */
+ public static String cleanText(@NonNull String text,
+ @NonNull CleanupMode mode, char commentChar) {
+ String toProcess = text;
+ boolean strip = false;
+ switch (mode) {
+ case VERBATIM:
+ return text;
+ case SCISSORS:
+ String cut = commentChar + CUT;
+ if (text.startsWith(cut)) {
+ return ""; //$NON-NLS-1$
+ }
+ int cutPos = text.indexOf('\n' + cut);
+ if (cutPos >= 0) {
+ toProcess = text.substring(0, cutPos + 1);
+ }
+ break;
+ case STRIP:
+ strip = true;
+ break;
+ case WHITESPACE:
+ break;
+ case DEFAULT:
+ default:
+ // Internal error; no translation
+ throw new IllegalArgumentException("Invalid clean-up mode " + mode); //$NON-NLS-1$
+ }
+ // WHITESPACE
+ StringBuilder result = new StringBuilder();
+ boolean lastWasEmpty = true;
+ for (String line : toProcess.split("\n")) { //$NON-NLS-1$
+ line = line.stripTrailing();
+ if (line.isEmpty()) {
+ if (!lastWasEmpty) {
+ result.append('\n');
+ lastWasEmpty = true;
+ }
+ } else if (!strip || !isComment(line, commentChar)) {
+ lastWasEmpty = false;
+ result.append(line).append('\n');
+ }
+ }
+ int bufferSize = result.length();
+ if (lastWasEmpty && bufferSize > 0) {
+ bufferSize--;
+ result.setLength(bufferSize);
+ }
+ if (bufferSize > 0 && !toProcess.endsWith("\n")) { //$NON-NLS-1$
+ if (result.charAt(bufferSize - 1) == '\n') {
+ result.setLength(bufferSize - 1);
+ }
+ }
+ return result.toString();
+ }
+
+ private static boolean isComment(String text, char commentChar) {
+ int len = text.length();
+ for (int i = 0; i < len; i++) {
+ char ch = text.charAt(i);
+ if (!Character.isWhitespace(ch)) {
+ return ch == commentChar;
+ }
+ }
+ return false;
+ }
} \ No newline at end of file
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 1ce3e312e2..d1d66d280e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -278,6 +278,54 @@ public class Config {
}
/**
+ * 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
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return an integer value from the configuration, or defaultValue.
+ * @since 6.1
+ */
+ public int getIntInRange(String section, String name, int minValue,
+ int maxValue, int defaultValue) {
+ return typedGetter.getIntInRange(this, 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 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
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return an integer value from the configuration, or defaultValue.
+ * @since 6.1
+ */
+ public int getIntInRange(String section, String subsection, String name,
+ int minValue, int maxValue, int defaultValue) {
+ return typedGetter.getIntInRange(this, section, subsection, name,
+ minValue, maxValue, defaultValue);
+ }
+
+ /**
* Obtain an integer value from the configuration.
*
* @param section
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 634e3f7f86..d4bd6c0e71 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -1,7 +1,8 @@
/*
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
* Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
- * Copyright (C) 2012, 2020, Robin Rosenberg and others
+ * Copyright (C) 2012-2013, Robin Rosenberg
+ * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com> 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
@@ -29,6 +30,48 @@ public final class ConfigConstants {
/** The "diff" section */
public static final String CONFIG_DIFF_SECTION = "diff";
+ /**
+ * The "tool" key within "diff" section
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_TOOL = "tool";
+
+ /**
+ * The "guitool" key within "diff" section
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_GUITOOL = "guitool";
+
+ /**
+ * The "difftool" section
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_DIFFTOOL_SECTION = "difftool";
+
+ /**
+ * The "prompt" key within "difftool" section
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_PROMPT = "prompt";
+
+ /**
+ * The "trustExitCode" key within "difftool" section
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_TRUST_EXIT_CODE = "trustExitCode";
+
+ /**
+ * The "cmd" key within "difftool.*." section
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_CMD = "cmd";
+
/** The "dfs" section */
public static final String CONFIG_DFS_SECTION = "dfs";
@@ -139,6 +182,13 @@ public final class ConfigConstants {
public static final String CONFIG_TAG_SECTION = "tag";
/**
+ * The "cleanup" key
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_CLEANUP = "cleanup";
+
+ /**
* The "gpgSign" key
*
* @since 5.2
@@ -279,6 +329,20 @@ public final class ConfigConstants {
/** The "remote" key */
public static final String CONFIG_KEY_REMOTE = "remote";
+ /**
+ * The "pushRemote" key.
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_PUSH_REMOTE = "pushRemote";
+
+ /**
+ * The "pushDefault" key.
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_PUSH_DEFAULT = "pushDefault";
+
/** The "merge" key */
public static final String CONFIG_KEY_MERGE = "merge";
@@ -778,6 +842,34 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT = "searchforreusetimeout";
/**
+ * The "push" section.
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_PUSH_SECTION = "push";
+
+ /**
+ * The "default" key.
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_DEFAULT = "default";
+
+ /**
+ * The "abbrev" key
+ *
+ * @since 6.1
+ */
+ public static final String CONFIG_KEY_ABBREV = "abbrev";
+
+ /**
+ * The "trustPackedRefsStat" key
+ *
+ * @since 6.1.1
+ */
+ public static final String CONFIG_KEY_TRUST_PACKED_REFS_STAT = "trustPackedRefsStat";
+
+ /**
* The "pack.preserveOldPacks" key
*
* @since 5.13.2
@@ -790,5 +882,4 @@ public final class ConfigConstants {
* @since 5.13.2
*/
public static final String CONFIG_KEY_PRUNE_PRESERVED = "prunepreserved";
-
}
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 92367ebd0c..cf2e69dbb5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -48,6 +48,15 @@ public final class Constants {
*/
public static final int OBJECT_ID_STRING_LENGTH = OBJECT_ID_LENGTH * 2;
+ /**
+ * The historic length of an abbreviated Git object hash string. Git 2.11
+ * changed this static number to a dynamically calculated one that scales
+ * as the repository grows.
+ *
+ * @since 6.1
+ */
+ public static final int OBJECT_ID_ABBREV_STRING_LENGTH = 7;
+
/** Special name for the "HEAD" symbolic-ref. */
public static final String HEAD = "HEAD";
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 f23c6e08d1..fc82a5fead 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -116,6 +116,27 @@ public class CoreConfig {
ALWAYS
}
+ /**
+ * Permissible values for {@code core.trustPackedRefsStat}.
+ *
+ * @since 6.1.1
+ */
+ public enum TrustPackedRefsStat {
+ /** Do not trust file attributes of the packed-refs file. */
+ NEVER,
+
+ /** 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. */
+ AFTER_OPEN,
+
+ /** {@code core.trustPackedRefsStat} defaults to this when it is
+ * not set */
+ UNSET
+ }
+
private final int compression;
private final int packIndexVersion;
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 9f96bce251..86409403b0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -120,6 +120,26 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
/** {@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);
+ 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().integerValueNotInRangeSubSection, section,
+ subsection, name, Integer.valueOf(val),
+ Integer.valueOf(minValue), Integer.valueOf(maxValue)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
public long getLong(Config config, String section, String subsection,
String name, long defaultValue) {
final String str = config.getString(section, subsection, name);
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 28ea927b14..df9fd47efa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -568,6 +568,9 @@ public class IndexDiff {
if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
smw.setTree(new DirCacheIterator(dirCache));
+ if (filter != null) {
+ smw.setFilter(filter);
+ }
smw.setBuilderFactory(factory);
while (smw.next()) {
IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
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 04262c07ae..70009cba35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -155,4 +155,14 @@ public abstract class ObjectDatabase implements AutoCloseable {
public ObjectDatabase newCachedDatabase() {
return this;
}
+
+ /**
+ * Get a quick, rough count of objects in this repository. Ignores loose
+ * objects. Returns {@code -1} if an exception occurs.
+ *
+ * @return quick, rough count of objects in this repository, {@code -1} if
+ * an exception occurs
+ * @since 6.1
+ */
+ public abstract long getApproximateObjectCount();
}
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 a2c7381ce7..26c3ff6718 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.lib;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -76,7 +78,7 @@ public abstract class ObjectReader implements AutoCloseable {
*/
public AbbreviatedObjectId abbreviate(AnyObjectId objectId)
throws IOException {
- return abbreviate(objectId, 7);
+ return abbreviate(objectId, OBJECT_ID_ABBREV_STRING_LENGTH);
}
/**
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 428a6b959c..93710299b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -14,6 +14,8 @@ package org.eclipse.jgit.lib;
import java.io.Serializable;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@@ -206,6 +208,20 @@ public class PersonIdent implements Serializable {
}
/**
+ * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's
+ * time stamp
+ *
+ * @param pi
+ * original {@link org.eclipse.jgit.lib.PersonIdent}
+ * @param aWhen
+ * local time as Instant
+ * @since 6.1
+ */
+ public PersonIdent(PersonIdent pi, Instant aWhen) {
+ this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset);
+ }
+
+ /**
* Construct a PersonIdent from simple data
*
* @param aName a {@link java.lang.String} object.
@@ -222,6 +238,27 @@ public class PersonIdent implements Serializable {
}
/**
+ * Construct a PersonIdent from simple data
+ *
+ * @param aName
+ * a {@link java.lang.String} object.
+ * @param aEmailAddress
+ * a {@link java.lang.String} object.
+ * @param aWhen
+ * local time stamp
+ * @param zoneId
+ * time zone id
+ * @since 6.1
+ */
+ public PersonIdent(final String aName, String aEmailAddress, Instant aWhen,
+ ZoneId zoneId) {
+ this(aName, aEmailAddress, aWhen.toEpochMilli(),
+ TimeZone.getTimeZone(zoneId)
+ .getOffset(aWhen
+ .toEpochMilli()) / (60 * 1000));
+ }
+
+ /**
* Copy a PersonIdent, but alter the clone's time stamp
*
* @param pi
@@ -304,6 +341,16 @@ public class PersonIdent implements Serializable {
}
/**
+ * Get when attribute as instant
+ *
+ * @return timestamp
+ * @since 6.1
+ */
+ public Instant getWhenAsInstant() {
+ return Instant.ofEpochMilli(when);
+ }
+
+ /**
* Get this person's declared time zone
*
* @return this person's declared time zone; null if time zone is unknown.
@@ -313,6 +360,16 @@ public class PersonIdent implements Serializable {
}
/**
+ * Get the time zone id
+ *
+ * @return the time zone id
+ * @since 6.1
+ */
+ public ZoneId getZoneId() {
+ return getTimeZone().toZoneId();
+ }
+
+ /**
* Get this person's declared time zone as minutes east of UTC.
*
* @return this person's declared time zone as minutes east of UTC. If the
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 0f2f6cff8a..c4eb8f10d5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
@@ -29,6 +29,13 @@ import org.eclipse.jgit.util.FS;
public interface TypedConfigGetter {
/**
+ * Use {@code Integer#MIN_VALUE} as unset int value
+ *
+ * @since 6.1
+ */
+ public static final int UNSET_INT = Integer.MIN_VALUE;
+
+ /**
* Get a boolean value from a git {@link Config}.
*
* @param config
@@ -87,6 +94,32 @@ public interface TypedConfigGetter {
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 6.1
+ */
+ int getIntInRange(Config config, String section, String subsection,
+ String name, int minValue, int maxValue, int defaultValue);
+
+ /**
* Obtain a long value from a git {@link Config}.
*
* @param config
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BooleanTriState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BooleanTriState.java
new file mode 100644
index 0000000000..44d3bb36e0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BooleanTriState.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 Simeon Andreev <simeon.danailov.andreev@gmail.com> 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.internal;
+
+/**
+ * A boolean value that can also have an unset state.
+ */
+public enum BooleanTriState {
+ /**
+ * Value equivalent to {@code true}.
+ */
+ TRUE,
+ /**
+ * Value equivalent to {@code false}.
+ */
+ FALSE,
+ /**
+ * Value is not set.
+ */
+ UNSET;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
index f7966a267f..e0c083f55c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -92,36 +92,62 @@ public class MergeMessageFormatter {
}
/**
- * Add section with conflicting paths to merge message.
+ * Add section with conflicting paths to merge message. Lines are prefixed
+ * with a hash.
*
* @param message
* the original merge message
* @param conflictingPaths
* the paths with conflicts
* @return merge message with conflicting paths added
+ * @deprecated since 6.1; use
+ * {@link #formatWithConflicts(String, Iterable, char)} instead
*/
+ @Deprecated
public String formatWithConflicts(String message,
List<String> conflictingPaths) {
+ return formatWithConflicts(message, conflictingPaths, '#');
+ }
+
+ /**
+ * Add section with conflicting paths to merge message.
+ *
+ * @param message
+ * the original merge message
+ * @param conflictingPaths
+ * the paths with conflicts
+ * @param commentChar
+ * comment character to use for prefixing the conflict lines
+ * @return merge message with conflicting paths added
+ * @since 6.1
+ */
+ public String formatWithConflicts(String message,
+ Iterable<String> conflictingPaths, char commentChar) {
StringBuilder sb = new StringBuilder();
String[] lines = message.split("\n"); //$NON-NLS-1$
int firstFooterLine = ChangeIdUtil.indexOfFirstFooterLine(lines);
- for (int i = 0; i < firstFooterLine; i++)
+ for (int i = 0; i < firstFooterLine; i++) {
sb.append(lines[i]).append('\n');
- if (firstFooterLine == lines.length && message.length() != 0)
+ }
+ if (firstFooterLine == lines.length && message.length() != 0) {
sb.append('\n');
- addConflictsMessage(conflictingPaths, sb);
- if (firstFooterLine < lines.length)
+ }
+ addConflictsMessage(conflictingPaths, sb, commentChar);
+ if (firstFooterLine < lines.length) {
sb.append('\n');
- for (int i = firstFooterLine; i < lines.length; i++)
+ }
+ for (int i = firstFooterLine; i < lines.length; i++) {
sb.append(lines[i]).append('\n');
+ }
return sb.toString();
}
- private static void addConflictsMessage(List<String> conflictingPaths,
- StringBuilder sb) {
- sb.append("Conflicts:\n"); //$NON-NLS-1$
+ private static void addConflictsMessage(Iterable<String> conflictingPaths,
+ StringBuilder sb, char commentChar) {
+ sb.append(commentChar).append(" Conflicts:\n"); //$NON-NLS-1$
for (String conflictingPath : conflictingPaths) {
- sb.append('\t').append(conflictingPath).append('\n');
+ sb.append(commentChar).append('\t').append(conflictingPath)
+ .append('\n');
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index 7767662867..b9ab1d1b7a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -3,7 +3,7 @@
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
* Copyright (C) 2012, Research In Motion Limited
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -276,11 +276,15 @@ public class ResolveMerger extends ThreeWayMerger {
private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
/**
- * Keeps {@link CheckoutMetadata} for {@link #checkout()} and
- * {@link #cleanUp()}.
+ * Keeps {@link CheckoutMetadata} for {@link #checkout()}.
*/
private Map<String, CheckoutMetadata> checkoutMetadata;
+ /**
+ * Keeps {@link CheckoutMetadata} for {@link #cleanUp()}.
+ */
+ private Map<String, CheckoutMetadata> cleanupMetadata;
+
private static MergeAlgorithm getMergeAlgorithm(Config config) {
SupportedAlgorithm diffAlg = config.getEnum(
CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
@@ -383,12 +387,14 @@ public class ResolveMerger extends ThreeWayMerger {
}
if (!inCore) {
checkoutMetadata = new HashMap<>();
+ cleanupMetadata = new HashMap<>();
}
try {
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
false);
} finally {
checkoutMetadata = null;
+ cleanupMetadata = null;
if (implicitDirCache) {
dircache.unlock();
}
@@ -447,7 +453,7 @@ public class ResolveMerger extends ThreeWayMerger {
DirCacheEntry entry = dc.getEntry(mpath);
if (entry != null) {
DirCacheCheckout.checkoutEntry(db, entry, reader, false,
- checkoutMetadata.get(mpath));
+ cleanupMetadata.get(mpath));
}
mpathsIt.remove();
}
@@ -501,22 +507,26 @@ public class ResolveMerger extends ThreeWayMerger {
* Remembers the {@link CheckoutMetadata} for the given path; it may be
* needed in {@link #checkout()} or in {@link #cleanUp()}.
*
+ * @param map
+ * to add the metadata to
* @param path
* of the current node
* @param attributes
- * for the current node
+ * to use for determining the metadata
* @throws IOException
* if the smudge filter cannot be determined
- * @since 5.1
+ * @since 6.1
*/
- protected void addCheckoutMetadata(String path, Attributes attributes)
+ protected void addCheckoutMetadata(Map<String, CheckoutMetadata> map,
+ String path, Attributes attributes)
throws IOException {
- if (checkoutMetadata != null) {
+ if (map != null) {
EolStreamType eol = EolStreamTypeUtil.detectStreamType(
- OperationType.CHECKOUT_OP, workingTreeOptions, attributes);
+ OperationType.CHECKOUT_OP, workingTreeOptions,
+ attributes);
CheckoutMetadata data = new CheckoutMetadata(eol,
- tw.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
- checkoutMetadata.put(path, data);
+ tw.getSmudgeCommand(attributes));
+ map.put(path, data);
}
}
@@ -529,15 +539,17 @@ public class ResolveMerger extends ThreeWayMerger {
* @param entry
* to add
* @param attributes
- * for the current entry
+ * the {@link Attributes} of the trees
* @throws IOException
* if the {@link CheckoutMetadata} cannot be determined
- * @since 5.1
+ * @since 6.1
*/
protected void addToCheckout(String path, DirCacheEntry entry,
- Attributes attributes) throws IOException {
+ Attributes[] attributes)
+ throws IOException {
toBeCheckedOut.put(path, entry);
- addCheckoutMetadata(path, attributes);
+ addCheckoutMetadata(cleanupMetadata, path, attributes[T_OURS]);
+ addCheckoutMetadata(checkoutMetadata, path, attributes[T_THEIRS]);
}
/**
@@ -549,7 +561,7 @@ public class ResolveMerger extends ThreeWayMerger {
* @param isFile
* whether it is a file
* @param attributes
- * for the entry
+ * to use for determining the {@link CheckoutMetadata}
* @throws IOException
* if the {@link CheckoutMetadata} cannot be determined
* @since 5.1
@@ -558,7 +570,7 @@ public class ResolveMerger extends ThreeWayMerger {
Attributes attributes) throws IOException {
toBeDeleted.add(path);
if (isFile) {
- addCheckoutMetadata(path, attributes);
+ addCheckoutMetadata(cleanupMetadata, path, attributes);
}
}
@@ -599,7 +611,7 @@ public class ResolveMerger extends ThreeWayMerger {
* see
* {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
* @param attributes
- * the attributes defined for this entry
+ * the {@link Attributes} for the three trees
* @return <code>false</code> if the merge will fail because the index entry
* didn't match ours or the working-dir file was dirty and a
* conflict occurred
@@ -607,12 +619,12 @@ public class ResolveMerger extends ThreeWayMerger {
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
* @throws org.eclipse.jgit.errors.CorruptObjectException
* @throws java.io.IOException
- * @since 4.9
+ * @since 6.1
*/
protected boolean processEntry(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
DirCacheBuildIterator index, WorkingTreeIterator work,
- boolean ignoreConflicts, Attributes attributes)
+ boolean ignoreConflicts, Attributes[] attributes)
throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException {
enterSubtree = true;
@@ -729,7 +741,7 @@ public class ResolveMerger extends ThreeWayMerger {
// Base, ours, and theirs all contain a folder: don't delete
return true;
}
- addDeletion(tw.getPathString(), nonTree(modeO), attributes);
+ addDeletion(tw.getPathString(), nonTree(modeO), attributes[T_OURS]);
return true;
}
@@ -772,7 +784,7 @@ public class ResolveMerger extends ThreeWayMerger {
if (nonTree(modeO) && nonTree(modeT)) {
// Check worktree before modifying files
boolean worktreeDirty = isWorktreeDirty(work, ourDce);
- if (!attributes.canBeContentMerged() && worktreeDirty) {
+ if (!attributes[T_OURS].canBeContentMerged() && worktreeDirty) {
return false;
}
@@ -791,7 +803,7 @@ public class ResolveMerger extends ThreeWayMerger {
mergeResults.put(tw.getPathString(), result);
unmergedPaths.add(tw.getPathString());
return true;
- } else if (!attributes.canBeContentMerged()) {
+ } else if (!attributes[T_OURS].canBeContentMerged()) {
// File marked as binary
switch (getContentMergeStrategy()) {
case OURS:
@@ -842,13 +854,16 @@ public class ResolveMerger extends ThreeWayMerger {
if (ignoreConflicts) {
result.setContainsConflicts(false);
}
- updateIndex(base, ours, theirs, result, attributes);
+ updateIndex(base, ours, theirs, result, attributes[T_OURS]);
String currentPath = tw.getPathString();
if (result.containsConflicts() && !ignoreConflicts) {
unmergedPaths.add(currentPath);
}
modifiedFiles.add(currentPath);
- addCheckoutMetadata(currentPath, attributes);
+ addCheckoutMetadata(cleanupMetadata, currentPath,
+ attributes[T_OURS]);
+ addCheckoutMetadata(checkoutMetadata, currentPath,
+ attributes[T_THEIRS]);
} else if (modeO != modeT) {
// OURS or THEIRS has been deleted
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
@@ -881,7 +896,8 @@ public class ResolveMerger extends ThreeWayMerger {
// markers). But also stage 0 of the index is filled
// with that content.
result.setContainsConflicts(false);
- updateIndex(base, ours, theirs, result, attributes);
+ updateIndex(base, ours, theirs, result,
+ attributes[T_OURS]);
} else {
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
0);
@@ -896,11 +912,9 @@ public class ResolveMerger extends ThreeWayMerger {
if (isWorktreeDirty(work, ourDce)) {
return false;
}
- if (nonTree(modeT)) {
- if (e != null) {
- addToCheckout(tw.getPathString(), e,
- attributes);
- }
+ if (nonTree(modeT) && e != null) {
+ addToCheckout(tw.getPathString(), e,
+ attributes);
}
}
@@ -945,14 +959,16 @@ public class ResolveMerger extends ThreeWayMerger {
*/
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
- Attributes attributes, ContentMergeStrategy strategy)
+ Attributes[] attributes, ContentMergeStrategy strategy)
throws BinaryBlobException, IOException {
+ // TW: The attributes here are used to determine the LFS smudge filter.
+ // Is doing a content merge on LFS items really a good idea??
RawText baseText = base == null ? RawText.EMPTY_TEXT
- : getRawText(base.getEntryObjectId(), attributes);
+ : getRawText(base.getEntryObjectId(), attributes[T_BASE]);
RawText ourText = ours == null ? RawText.EMPTY_TEXT
- : getRawText(ours.getEntryObjectId(), attributes);
+ : getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
- : getRawText(theirs.getEntryObjectId(), attributes);
+ : getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
mergeAlgorithm.setContentMergeStrategy(strategy);
return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
ourText, theirsText);
@@ -1342,7 +1358,7 @@ public class ResolveMerger extends ThreeWayMerger {
tw = new NameConflictTreeWalk(db, reader);
tw.addTree(baseTree);
- tw.addTree(headTree);
+ tw.setHead(tw.addTree(headTree));
tw.addTree(mergeTree);
int dciPos = tw.addTree(buildIt);
if (workingTreeIterator != null) {
@@ -1403,6 +1419,13 @@ public class ResolveMerger extends ThreeWayMerger {
boolean hasAttributeNodeProvider = treeWalk
.getAttributesNodeProvider() != null;
while (treeWalk.next()) {
+ Attributes[] attributes = { NO_ATTRIBUTES, NO_ATTRIBUTES,
+ NO_ATTRIBUTES };
+ if (hasAttributeNodeProvider) {
+ attributes[T_BASE] = treeWalk.getAttributes(T_BASE);
+ attributes[T_OURS] = treeWalk.getAttributes(T_OURS);
+ attributes[T_THEIRS] = treeWalk.getAttributes(T_THEIRS);
+ }
if (!processEntry(
treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
treeWalk.getTree(T_OURS, CanonicalTreeParser.class),
@@ -1410,9 +1433,7 @@ public class ResolveMerger extends ThreeWayMerger {
treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
WorkingTreeIterator.class) : null,
- ignoreConflicts, hasAttributeNodeProvider
- ? treeWalk.getAttributes()
- : NO_ATTRIBUTES)) {
+ ignoreConflicts, attributes)) {
cleanUp();
return false;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index e6f9580bf7..4e48a5c328 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.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
@@ -139,7 +139,7 @@ public class ObjectWalk extends RevWalk {
* the repository the walker will obtain data from.
*/
public ObjectWalk(Repository repo) {
- this(repo.newObjectReader());
+ this(repo.newObjectReader(), true);
}
/**
@@ -151,7 +151,11 @@ public class ObjectWalk extends RevWalk {
* required.
*/
public ObjectWalk(ObjectReader or) {
- super(or);
+ this(or, false);
+ }
+
+ private ObjectWalk(ObjectReader or, boolean closeReader) {
+ super(or, closeReader);
setRetainBody(false);
rootObjects = new ArrayList<>();
pendingObjects = new BlockObjQueue();
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 8d571f5b14..a50eaf1a8a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -215,7 +215,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
this(or, false);
}
- private RevWalk(ObjectReader or, boolean closeReader) {
+ RevWalk(ObjectReader or, boolean closeReader) {
reader = or;
idBuffer = new MutableObjectId();
objects = new ObjectIdOwnerMap<>();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
index fda7a8152a..c8774d546a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.com> and others
+ * Copyright (C) 2017, 2022 David Pursehouse <david.pursehouse@gmail.com> 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
@@ -10,7 +10,10 @@
package org.eclipse.jgit.transport;
+import java.util.Locale;
+
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.util.StringUtils;
/**
@@ -19,8 +22,9 @@ import org.eclipse.jgit.util.StringUtils;
* @since 4.9
*/
public class PushConfig {
+
/**
- * Config values for push.recurseSubmodules.
+ * Git config values for {@code push.recurseSubmodules}.
*/
public enum PushRecurseSubmodulesMode implements Config.ConfigEnum {
/**
@@ -59,4 +63,100 @@ public class PushConfig {
|| configValue.equalsIgnoreCase(s);
}
}
+
+ /**
+ * Git config values for {@code push.default}.
+ *
+ * @since 6.1
+ */
+ public enum PushDefault implements Config.ConfigEnum {
+
+ /**
+ * Do not push if there are no explicit refspecs.
+ */
+ NOTHING,
+
+ /**
+ * Push the current branch to an upstream branch of the same name.
+ */
+ CURRENT,
+
+ /**
+ * Push the current branch to an upstream branch determined by git
+ * config {@code branch.<currentBranch>.merge}.
+ */
+ UPSTREAM("tracking"), //$NON-NLS-1$
+
+ /**
+ * Like {@link #UPSTREAM}, but only if the upstream name is the same as
+ * the name of the current local branch.
+ */
+ SIMPLE,
+
+ /**
+ * Push all current local branches that match a configured push refspec
+ * of the remote configuration.
+ */
+ MATCHING;
+
+ private final String alias;
+
+ private PushDefault() {
+ alias = null;
+ }
+
+ private PushDefault(String alias) {
+ this.alias = alias;
+ }
+
+ @Override
+ public String toConfigValue() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+
+ @Override
+ public boolean matchConfigValue(String in) {
+ return toConfigValue().equalsIgnoreCase(in)
+ || (alias != null && alias.equalsIgnoreCase(in));
+ }
+ }
+
+ private final PushRecurseSubmodulesMode recurseSubmodules;
+
+ private final PushDefault pushDefault;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param config
+ * {@link Config} to fill the {@link PushConfig} from
+ * @since 6.1
+ */
+ public PushConfig(Config config) {
+ recurseSubmodules = config.getEnum(ConfigConstants.CONFIG_PUSH_SECTION,
+ null, ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES,
+ PushRecurseSubmodulesMode.NO);
+ pushDefault = config.getEnum(ConfigConstants.CONFIG_PUSH_SECTION, null,
+ ConfigConstants.CONFIG_KEY_DEFAULT, PushDefault.SIMPLE);
+ }
+
+ /**
+ * Retrieves the value of git config {@code push.recurseSubmodules}.
+ *
+ * @return the value
+ * @since 6.1
+ */
+ public PushRecurseSubmodulesMode getRecurseSubmodules() {
+ return recurseSubmodules;
+ }
+
+ /**
+ * Retrieves the value of git config {@code push.default}.
+ *
+ * @return the value
+ * @since 6.1
+ */
+ public PushDefault getPushDefault() {
+ return pushDefault;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index a244c55a38..942dad46e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -18,11 +18,15 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.hooks.PrePushHook;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
@@ -58,6 +62,8 @@ class PushProcess {
/** A list of option strings associated with this push */
private List<String> pushOptions;
+ private final PrePushHook prePush;
+
/**
* Create process for specified transport and refs updates specification.
*
@@ -66,12 +72,14 @@ class PushProcess {
* connection.
* @param toPush
* specification of refs updates (and local tracking branches).
- *
+ * @param prePush
+ * {@link PrePushHook} to run after the remote advertisement has
+ * been gotten
* @throws TransportException
*/
- PushProcess(final Transport transport,
- final Collection<RemoteRefUpdate> toPush) throws TransportException {
- this(transport, toPush, null);
+ PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
+ PrePushHook prePush) throws TransportException {
+ this(transport, toPush, prePush, null);
}
/**
@@ -82,16 +90,19 @@ class PushProcess {
* connection.
* @param toPush
* specification of refs updates (and local tracking branches).
+ * @param prePush
+ * {@link PrePushHook} to run after the remote advertisement has
+ * been gotten
* @param out
* OutputStream to write messages to
* @throws TransportException
*/
- PushProcess(final Transport transport,
- final Collection<RemoteRefUpdate> toPush, OutputStream out)
- throws TransportException {
+ PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
+ PrePushHook prePush, OutputStream out) throws TransportException {
this.walker = new RevWalk(transport.local);
this.transport = transport;
this.toPush = new LinkedHashMap<>();
+ this.prePush = prePush;
this.out = out;
this.pushOptions = transport.getPushOptions();
for (RemoteRefUpdate rru : toPush) {
@@ -129,10 +140,38 @@ class PushProcess {
res.setAdvertisedRefs(transport.getURI(), connection
.getRefsMap());
res.peerUserAgent = connection.getPeerUserAgent();
- res.setRemoteUpdates(toPush);
monitor.endTask();
+ Map<String, RemoteRefUpdate> expanded = expandMatching();
+ toPush.clear();
+ toPush.putAll(expanded);
+
+ res.setRemoteUpdates(toPush);
final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
+ List<RemoteRefUpdate> willBeAttempted = preprocessed.values()
+ .stream().filter(u -> {
+ switch (u.getStatus()) {
+ case NON_EXISTING:
+ case REJECTED_NODELETE:
+ case REJECTED_NONFASTFORWARD:
+ case REJECTED_OTHER_REASON:
+ case REJECTED_REMOTE_CHANGED:
+ case UP_TO_DATE:
+ return false;
+ default:
+ return true;
+ }
+ }).collect(Collectors.toList());
+ if (!willBeAttempted.isEmpty()) {
+ if (prePush != null) {
+ try {
+ prePush.setRefs(willBeAttempted);
+ prePush.call();
+ } catch (AbortedByHookException | IOException e) {
+ throw new TransportException(e.getMessage(), e);
+ }
+ }
+ }
if (transport.isDryRun())
modifyUpdatesForDryRun();
else if (!preprocessed.isEmpty())
@@ -201,25 +240,8 @@ class PushProcess {
continue;
}
- // check for fast-forward:
- // - both old and new ref must point to commits, AND
- // - both of them must be known for us, exist in repository, AND
- // - old commit must be ancestor of new commit
- boolean fastForward = true;
- try {
- RevObject oldRev = walker.parseAny(advertisedOld);
- final RevObject newRev = walker.parseAny(rru.getNewObjectId());
- if (!(oldRev instanceof RevCommit)
- || !(newRev instanceof RevCommit)
- || !walker.isMergedInto((RevCommit) oldRev,
- (RevCommit) newRev))
- fastForward = false;
- } catch (MissingObjectException x) {
- fastForward = false;
- } catch (Exception x) {
- throw new TransportException(transport.getURI(), MessageFormat.format(
- JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x);
- }
+ boolean fastForward = isFastForward(advertisedOld,
+ rru.getNewObjectId());
rru.setFastForward(fastForward);
if (!fastForward && !rru.isForceUpdate()) {
rru.setStatus(Status.REJECTED_NONFASTFORWARD);
@@ -233,6 +255,134 @@ class PushProcess {
return result;
}
+ /**
+ * Determines whether an update from {@code oldOid} to {@code newOid} is a
+ * fast-forward update:
+ * <ul>
+ * <li>both old and new must be commits, AND</li>
+ * <li>both of them must be known to us and exist in the repository,
+ * AND</li>
+ * <li>the old commit must be an ancestor of the new commit.</li>
+ * </ul>
+ *
+ * @param oldOid
+ * {@link ObjectId} of the old commit
+ * @param newOid
+ * {@link ObjectId} of the new commit
+ * @return {@code true} if the update fast-forwards, {@code false} otherwise
+ * @throws TransportException
+ */
+ private boolean isFastForward(ObjectId oldOid, ObjectId newOid)
+ throws TransportException {
+ try {
+ RevObject oldRev = walker.parseAny(oldOid);
+ RevObject newRev = walker.parseAny(newOid);
+ if (!(oldRev instanceof RevCommit) || !(newRev instanceof RevCommit)
+ || !walker.isMergedInto((RevCommit) oldRev,
+ (RevCommit) newRev)) {
+ return false;
+ }
+ } catch (MissingObjectException x) {
+ return false;
+ } catch (Exception x) {
+ throw new TransportException(transport.getURI(),
+ MessageFormat.format(JGitText
+ .get().readingObjectsFromLocalRepositoryFailed,
+ x.getMessage()),
+ x);
+ }
+ return true;
+ }
+
+ /**
+ * Expands all placeholder {@link RemoteRefUpdate}s for "matching"
+ * {@link RefSpec}s ":" in {@link #toPush} and returns the resulting map in
+ * which the placeholders have been replaced by their expansion.
+ *
+ * @return a new map of {@link RemoteRefUpdate}s keyed by remote name
+ * @throws TransportException
+ * if the expansion results in duplicate updates
+ */
+ private Map<String, RemoteRefUpdate> expandMatching()
+ throws TransportException {
+ Map<String, RemoteRefUpdate> result = new LinkedHashMap<>();
+ boolean hadMatch = false;
+ for (RemoteRefUpdate update : toPush.values()) {
+ if (update.isMatching()) {
+ if (hadMatch) {
+ throw new TransportException(MessageFormat.format(
+ JGitText.get().duplicateRemoteRefUpdateIsIllegal,
+ ":")); //$NON-NLS-1$
+ }
+ expandMatching(result, update);
+ hadMatch = true;
+ } else if (result.put(update.getRemoteName(), update) != null) {
+ throw new TransportException(MessageFormat.format(
+ JGitText.get().duplicateRemoteRefUpdateIsIllegal,
+ update.getRemoteName()));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Expands the placeholder {@link RemoteRefUpdate} {@code match} for a
+ * "matching" {@link RefSpec} ":" or "+:" and puts the expansion into the
+ * given map {@code updates}.
+ *
+ * @param updates
+ * map to put the expansion in
+ * @param match
+ * the placeholder {@link RemoteRefUpdate} to expand
+ *
+ * @throws TransportException
+ * if the expansion results in duplicate updates, or the local
+ * branches cannot be determined
+ */
+ private void expandMatching(Map<String, RemoteRefUpdate> updates,
+ RemoteRefUpdate match) throws TransportException {
+ try {
+ Map<String, Ref> advertisement = connection.getRefsMap();
+ Collection<RefSpec> fetchSpecs = match.getFetchSpecs();
+ boolean forceUpdate = match.isForceUpdate();
+ for (Ref local : transport.local.getRefDatabase()
+ .getRefsByPrefix(Constants.R_HEADS)) {
+ if (local.isSymbolic()) {
+ continue;
+ }
+ String name = local.getName();
+ Ref advertised = advertisement.get(name);
+ if (advertised == null || advertised.isSymbolic()) {
+ continue;
+ }
+ ObjectId oldOid = advertised.getObjectId();
+ if (oldOid == null || ObjectId.zeroId().equals(oldOid)) {
+ continue;
+ }
+ ObjectId newOid = local.getObjectId();
+ if (newOid == null || ObjectId.zeroId().equals(newOid)) {
+ continue;
+ }
+
+ RemoteRefUpdate rru = new RemoteRefUpdate(transport.local, name,
+ newOid, name, forceUpdate,
+ Transport.findTrackingRefName(name, fetchSpecs),
+ oldOid);
+ if (updates.put(rru.getRemoteName(), rru) != null) {
+ throw new TransportException(MessageFormat.format(
+ JGitText.get().duplicateRemoteRefUpdateIsIllegal,
+ rru.getRemoteName()));
+ }
+ }
+ } catch (IOException x) {
+ throw new TransportException(transport.getURI(),
+ MessageFormat.format(JGitText
+ .get().readingObjectsFromLocalRepositoryFailed,
+ x.getMessage()),
+ x);
+ }
+ }
+
private Map<String, RemoteRefUpdate> rejectAll() {
for (RemoteRefUpdate rru : toPush.values()) {
if (rru.getStatus() == Status.NOT_ATTEMPTED) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
index ac357afdae..56d0036a20 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -12,11 +12,11 @@ package org.eclipse.jgit.transport;
import java.io.Serializable;
import java.text.MessageFormat;
+import java.util.Objects;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.util.References;
/**
* Describes how refs in one repository copy into another repository.
@@ -50,6 +50,9 @@ public class RefSpec implements Serializable {
/** Is this specification actually a wildcard match? */
private boolean wildcard;
+ /** Is this the special ":" RefSpec? */
+ private boolean matching;
+
/**
* How strict to be about wildcards.
*
@@ -71,6 +74,7 @@ public class RefSpec implements Serializable {
*/
ALLOW_MISMATCH
}
+
/** Whether a wildcard is allowed on one side but not the other. */
private WildcardMode allowMismatchedWildcards;
@@ -87,6 +91,7 @@ public class RefSpec implements Serializable {
* applications, as at least one field must be set to match a source name.
*/
public RefSpec() {
+ matching = false;
force = false;
wildcard = false;
srcName = Constants.HEAD;
@@ -133,17 +138,25 @@ public class RefSpec implements Serializable {
s = s.substring(1);
}
+ boolean matchPushSpec = false;
final int c = s.lastIndexOf(':');
if (c == 0) {
s = s.substring(1);
- if (isWildcard(s)) {
+ if (s.isEmpty()) {
+ matchPushSpec = true;
wildcard = true;
- if (mode == WildcardMode.REQUIRE_MATCH) {
- throw new IllegalArgumentException(MessageFormat
- .format(JGitText.get().invalidWildcards, spec));
+ srcName = Constants.R_HEADS + '*';
+ dstName = srcName;
+ } else {
+ if (isWildcard(s)) {
+ wildcard = true;
+ if (mode == WildcardMode.REQUIRE_MATCH) {
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().invalidWildcards, spec));
+ }
}
+ dstName = checkValid(s);
}
- dstName = checkValid(s);
} else if (c > 0) {
String src = s.substring(0, c);
String dst = s.substring(c + 1);
@@ -168,6 +181,7 @@ public class RefSpec implements Serializable {
}
srcName = checkValid(s);
}
+ matching = matchPushSpec;
}
/**
@@ -195,6 +209,7 @@ public class RefSpec implements Serializable {
}
private RefSpec(RefSpec p) {
+ matching = false;
force = p.isForceUpdate();
wildcard = p.isWildcard();
srcName = p.getSource();
@@ -203,6 +218,17 @@ public class RefSpec implements Serializable {
}
/**
+ * Tells whether this {@link RefSpec} is the special "matching" RefSpec ":"
+ * for pushing.
+ *
+ * @return whether this is a "matching" RefSpec
+ * @since 6.1
+ */
+ public boolean isMatching() {
+ return matching;
+ }
+
+ /**
* Check if this specification wants to forcefully update the destination.
*
* @return true if this specification asks for updates without merge tests.
@@ -220,6 +246,7 @@ public class RefSpec implements Serializable {
*/
public RefSpec setForceUpdate(boolean forceUpdate) {
final RefSpec r = new RefSpec(this);
+ r.matching = matching;
r.force = forceUpdate;
return r;
}
@@ -322,8 +349,7 @@ public class RefSpec implements Serializable {
* The wildcard status of the new source disagrees with the
* wildcard status of the new destination.
*/
- public RefSpec setSourceDestination(final String source,
- final String destination) {
+ public RefSpec setSourceDestination(String source, String destination) {
if (isWildcard(source) != isWildcard(destination))
throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
final RefSpec r = new RefSpec(this);
@@ -541,37 +567,36 @@ public class RefSpec implements Serializable {
if (!(obj instanceof RefSpec))
return false;
final RefSpec b = (RefSpec) obj;
- if (isForceUpdate() != b.isForceUpdate())
- return false;
- if (isWildcard() != b.isWildcard())
+ if (isForceUpdate() != b.isForceUpdate()) {
return false;
- if (!eq(getSource(), b.getSource()))
- return false;
- if (!eq(getDestination(), b.getDestination()))
- return false;
- return true;
- }
-
- private static boolean eq(String a, String b) {
- if (References.isSameObject(a, b)) {
- return true;
}
- if (a == null || b == null)
+ if (isMatching()) {
+ return b.isMatching();
+ } else if (b.isMatching()) {
return false;
- return a.equals(b);
+ }
+ return isWildcard() == b.isWildcard()
+ && Objects.equals(getSource(), b.getSource())
+ && Objects.equals(getDestination(), b.getDestination());
}
/** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
- if (isForceUpdate())
+ if (isForceUpdate()) {
r.append('+');
- if (getSource() != null)
- r.append(getSource());
- if (getDestination() != null) {
+ }
+ if (isMatching()) {
r.append(':');
- r.append(getDestination());
+ } else {
+ if (getSource() != null) {
+ r.append(getSource());
+ }
+ if (getDestination() != null) {
+ r.append(':');
+ r.append(getDestination());
+ }
}
return r.toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 43eaac7927..218e62c10a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -12,7 +12,9 @@ package org.eclipse.jgit.transport;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.Collection;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -116,6 +118,12 @@ public class RemoteRefUpdate {
private RefUpdate localUpdate;
/**
+ * If set, the RemoteRefUpdate is a placeholder for the "matching" RefSpec
+ * to be expanded after the advertisements have been received in a push.
+ */
+ private Collection<RefSpec> fetchSpecs;
+
+ /**
* Construct remote ref update request by providing an update specification.
* Object is created with default
* {@link org.eclipse.jgit.transport.RemoteRefUpdate.Status#NOT_ATTEMPTED}
@@ -157,9 +165,8 @@ public class RemoteRefUpdate {
* @throws java.lang.IllegalArgumentException
* if some required parameter was null
*/
- public RemoteRefUpdate(final Repository localDb, final String srcRef,
- final String remoteName, final boolean forceUpdate,
- final String localName, final ObjectId expectedOldObjectId)
+ public RemoteRefUpdate(Repository localDb, String srcRef, String remoteName,
+ boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
throws IOException {
this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef)
: ObjectId.zeroId(), remoteName, forceUpdate, localName,
@@ -203,9 +210,8 @@ public class RemoteRefUpdate {
* @throws java.lang.IllegalArgumentException
* if some required parameter was null
*/
- public RemoteRefUpdate(final Repository localDb, final Ref srcRef,
- final String remoteName, final boolean forceUpdate,
- final String localName, final ObjectId expectedOldObjectId)
+ public RemoteRefUpdate(Repository localDb, Ref srcRef, String remoteName,
+ boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
throws IOException {
this(localDb, srcRef != null ? srcRef.getName() : null,
srcRef != null ? srcRef.getObjectId() : null, remoteName,
@@ -255,28 +261,41 @@ public class RemoteRefUpdate {
* @throws java.lang.IllegalArgumentException
* if some required parameter was null
*/
- public RemoteRefUpdate(final Repository localDb, final String srcRef,
- final ObjectId srcId, final String remoteName,
- final boolean forceUpdate, final String localName,
- final ObjectId expectedOldObjectId) throws IOException {
- if (remoteName == null)
- throw new IllegalArgumentException(JGitText.get().remoteNameCannotBeNull);
- if (srcId == null && srcRef != null)
- throw new IOException(MessageFormat.format(
- JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef));
-
- if (srcRef != null)
+ public RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
+ String remoteName, boolean forceUpdate, String localName,
+ ObjectId expectedOldObjectId) throws IOException {
+ this(localDb, srcRef, srcId, remoteName, forceUpdate, localName, null,
+ expectedOldObjectId);
+ }
+
+ private RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
+ String remoteName, boolean forceUpdate, String localName,
+ Collection<RefSpec> fetchSpecs, ObjectId expectedOldObjectId)
+ throws IOException {
+ if (fetchSpecs == null) {
+ if (remoteName == null) {
+ throw new IllegalArgumentException(
+ JGitText.get().remoteNameCannotBeNull);
+ }
+ if (srcId == null && srcRef != null) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().sourceRefDoesntResolveToAnyObject,
+ srcRef));
+ }
+ }
+ if (srcRef != null) {
this.srcRef = srcRef;
- else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
+ } else if (srcId != null && !srcId.equals(ObjectId.zeroId())) {
this.srcRef = srcId.name();
- else
+ } else {
this.srcRef = null;
-
- if (srcId != null)
+ }
+ if (srcId != null) {
this.newObjectId = srcId;
- else
+ } else {
this.newObjectId = ObjectId.zeroId();
-
+ }
+ this.fetchSpecs = fetchSpecs;
this.remoteName = remoteName;
this.forceUpdate = forceUpdate;
if (localName != null && localDb != null) {
@@ -292,8 +311,9 @@ public class RemoteRefUpdate {
? localUpdate.getOldObjectId()
: ObjectId.zeroId(),
newObjectId);
- } else
+ } else {
trackingRefUpdate = null;
+ }
this.localDb = localDb;
this.expectedOldObjectId = expectedOldObjectId;
this.status = Status.NOT_ATTEMPTED;
@@ -316,11 +336,57 @@ public class RemoteRefUpdate {
* local tracking branch or srcRef of base object no longer can
* be resolved to any object.
*/
- public RemoteRefUpdate(final RemoteRefUpdate base,
- final ObjectId newExpectedOldObjectId) throws IOException {
- this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
+ public RemoteRefUpdate(RemoteRefUpdate base,
+ ObjectId newExpectedOldObjectId) throws IOException {
+ this(base.localDb, base.srcRef, base.newObjectId, base.remoteName,
+ base.forceUpdate,
(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
- .getLocalName()), newExpectedOldObjectId);
+ .getLocalName()),
+ base.fetchSpecs, newExpectedOldObjectId);
+ }
+
+ /**
+ * Creates a "placeholder" update for the "matching" RefSpec ":".
+ *
+ * @param localDb
+ * local repository to push from
+ * @param forceUpdate
+ * whether non-fast-forward updates shall be allowed
+ * @param fetchSpecs
+ * The fetch {@link RefSpec}s to use when this placeholder is
+ * expanded to determine remote tracking branch updates
+ */
+ RemoteRefUpdate(Repository localDb, boolean forceUpdate,
+ @NonNull Collection<RefSpec> fetchSpecs) {
+ this.localDb = localDb;
+ this.forceUpdate = forceUpdate;
+ this.fetchSpecs = fetchSpecs;
+ this.trackingRefUpdate = null;
+ this.srcRef = null;
+ this.remoteName = null;
+ this.newObjectId = null;
+ this.status = Status.NOT_ATTEMPTED;
+ }
+
+ /**
+ * Tells whether this {@link RemoteRefUpdate} is a placeholder for a
+ * "matching" {@link RefSpec}.
+ *
+ * @return {@code true} if this is a placeholder, {@code false} otherwise
+ * @since 6.1
+ */
+ public boolean isMatching() {
+ return fetchSpecs != null;
+ }
+
+ /**
+ * Retrieves the fetch {@link RefSpec}s of this {@link RemoteRefUpdate}.
+ *
+ * @return the fetch {@link RefSpec}s, or {@code null} if
+ * {@code this.}{@link #isMatching()} {@code == false}
+ */
+ Collection<RefSpec> getFetchSpecs() {
+ return fetchSpecs;
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
index 212a4e46c1..48cacf0964 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -44,6 +44,14 @@ public final class SshConstants {
// Config file keys
+ /**
+ * Property to control whether private keys are added to an SSH agent, if
+ * one is running, after having been loaded.
+ *
+ * @since 6.1
+ */
+ public static final String ADD_KEYS_TO_AGENT = "AddKeysToAgent";
+
/** Key in an ssh config file. */
public static final String BATCH_MODE = "BatchMode";
@@ -62,6 +70,15 @@ public final class SshConstants {
/** Key in an ssh config file. */
public static final String CONNECTION_ATTEMPTS = "ConnectionAttempts";
+ /**
+ * An OpenSSH time value for the connection timeout. In OpenSSH, this
+ * includes everything until the end of the initial key exchange; in JGit it
+ * covers only the underlying TCP connect.
+ *
+ * @since 6.1
+ */
+ public static final String CONNECT_TIMEOUT = "ConnectTimeout";
+
/** Key in an ssh config file. */
public static final String CONTROL_PATH = "ControlPath";
@@ -159,6 +176,14 @@ public final class SshConstants {
/** Key in an ssh config file. */
public static final String REMOTE_FORWARD = "RemoteForward";
+ /**
+ * (Absolute) path to a middleware library the SSH agent shall use to load
+ * SK (U2F) keys.
+ *
+ * @since 6.1
+ */
+ public static final String SECURITY_KEY_PROVIDER = "SecurityKeyProvider";
+
/** Key in an ssh config file. */
public static final String SEND_ENV = "SendEnv";
@@ -229,4 +254,12 @@ public final class SshConstants {
public static final String[] DEFAULT_IDENTITIES = { //
ID_RSA, ID_DSA, ID_ECDSA, ID_ED25519
};
+
+ /**
+ * Name of the environment variable holding the Unix domain socket for
+ * communication with an SSH agent.
+ *
+ * @since 6.1
+ */
+ public static final String ENV_SSH_AUTH_SOCKET = "SSH_AUTH_SOCK";
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
index 696ca7cf46..51bc07cb94 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
@@ -12,6 +12,8 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
+
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
@@ -184,9 +186,13 @@ public class TrackingRefUpdate {
if (forceUpdate)
sb.append(" (forced)");
sb.append(" ");
- sb.append(oldObjectId == null ? "" : oldObjectId.abbreviate(7).name());
+ sb.append(oldObjectId == null ? ""
+ : oldObjectId.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+ .name());
sb.append("..");
- sb.append(newObjectId == null ? "" : newObjectId.abbreviate(7).name());
+ sb.append(newObjectId == null ? ""
+ : newObjectId.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH)
+ .name());
sb.append("]");
return sb.toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 5b781ac25f..0eab4434ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -2,7 +2,7 @@
* Copyright (C) 2008, 2009 Google Inc.
* Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.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
@@ -40,7 +40,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
-import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.hooks.Hooks;
@@ -590,6 +589,11 @@ public abstract class Transport implements AutoCloseable {
final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
for (RefSpec spec : procRefs) {
+ if (spec.isMatching()) {
+ result.add(new RemoteRefUpdate(db, spec.isForceUpdate(),
+ fetchSpecs));
+ continue;
+ }
String srcSpec = spec.getSource();
final Ref srcRef = db.findRef(srcSpec);
if (srcRef != null)
@@ -656,14 +660,18 @@ public abstract class Transport implements AutoCloseable {
private static Collection<RefSpec> expandPushWildcardsFor(
final Repository db, final Collection<RefSpec> specs)
throws IOException {
- final List<Ref> localRefs = db.getRefDatabase().getRefs();
final Collection<RefSpec> procRefs = new LinkedHashSet<>();
+ List<Ref> localRefs = null;
for (RefSpec spec : specs) {
- if (spec.isWildcard()) {
+ if (!spec.isMatching() && spec.isWildcard()) {
+ if (localRefs == null) {
+ localRefs = db.getRefDatabase().getRefs();
+ }
for (Ref localRef : localRefs) {
- if (spec.matchSource(localRef))
+ if (spec.matchSource(localRef)) {
procRefs.add(spec.expandFromSource(localRef));
+ }
}
} else {
procRefs.add(spec);
@@ -672,7 +680,7 @@ public abstract class Transport implements AutoCloseable {
return procRefs;
}
- private static String findTrackingRefName(final String remoteName,
+ static String findTrackingRefName(final String remoteName,
final Collection<RefSpec> fetchSpecs) {
// try to find matching tracking refs
for (RefSpec fetchSpec : fetchSpecs) {
@@ -1371,16 +1379,9 @@ public abstract class Transport implements AutoCloseable {
if (toPush.isEmpty())
throw new TransportException(JGitText.get().nothingToPush);
}
- if (prePush != null) {
- try {
- prePush.setRefs(toPush);
- prePush.call();
- } catch (AbortedByHookException | IOException e) {
- throw new TransportException(e.getMessage(), e);
- }
- }
- final PushProcess pushProcess = new PushProcess(this, toPush, out);
+ final PushProcess pushProcess = new PushProcess(this, toPush, prePush,
+ out);
return pushProcess.execute(monitor);
}
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 6cb8fe43c0..1617c5063d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -2358,7 +2358,7 @@ public class UploadPack implements Closeable {
: req.getDepth() - 1;
pw.setShallowPack(req.getDepth(), unshallowCommits);
- @SuppressWarnings("resource") // Ownership is transferred below
+ // Ownership is transferred below
DepthWalk.RevWalk dw = new DepthWalk.RevWalk(
walk.getObjectReader(), walkDepth);
dw.setDeepenSince(req.getDeepenSince());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
index 3d15ef5e72..046f395049 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2010, Google Inc. and others
+ * Copyright (C) 2009-2022, Google Inc. 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
@@ -18,11 +18,11 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
/**
* Default resolver serving from the local filesystem.
@@ -67,7 +67,7 @@ public class FileResolver<C> implements RepositoryResolver<C> {
if (isUnreasonableName(name))
throw new RepositoryNotFoundException(name);
- Repository db = exports.get(nameWithDotGit(name));
+ Repository db = exports.get(StringUtils.nameWithDotGit(name));
if (db != null) {
db.incrementOpen();
return db;
@@ -154,7 +154,7 @@ public class FileResolver<C> implements RepositoryResolver<C> {
* the repository instance.
*/
public void exportRepository(String name, Repository db) {
- exports.put(nameWithDotGit(name), db);
+ exports.put(StringUtils.nameWithDotGit(name), db);
}
/**
@@ -197,12 +197,6 @@ public class FileResolver<C> implements RepositoryResolver<C> {
return false;
}
- private static String nameWithDotGit(String name) {
- if (name.endsWith(Constants.DOT_GIT_EXT))
- return name;
- return name + Constants.DOT_GIT_EXT;
- }
-
private static boolean isUnreasonableName(String name) {
if (name.length() == 0)
return true; // no empty paths
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 1f614e31f6..8269666d26 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -1,6 +1,6 @@
/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2009 Google Inc.
+ * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.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
@@ -14,6 +14,7 @@ package org.eclipse.jgit.treewalk;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -73,6 +74,7 @@ import org.eclipse.jgit.util.io.EolStreamTypeUtil;
* threads.
*/
public class TreeWalk implements AutoCloseable, AttributesProvider {
+
private static final AbstractTreeIterator[] NO_TREES = {};
/**
@@ -92,7 +94,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
/**
- * Type of operation you want to retrieve the git attributes for.
+ * Type of operation you want to retrieve the git attributes for.
*/
private OperationType operationType = OperationType.CHECKOUT_OP;
@@ -284,11 +286,20 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
AbstractTreeIterator currentHead;
- /** Cached attribute for the current entry */
- private Attributes attrs = null;
+ /**
+ * Cached attributes for the current entry; per tree. Index i+1 is for tree
+ * i; index 0 is for the deprecated legacy behavior.
+ */
+ private Attributes[] attrs;
+
+ /**
+ * Cached attributes handler; per tree. Index i+1 is for tree i; index 0 is
+ * for the deprecated legacy behavior.
+ */
+ private AttributesHandler[] attributesHandlers;
- /** Cached attributes handler */
- private AttributesHandler attributesHandler;
+ /** Can be set to identify the tree to use for {@link #getAttributes()}. */
+ private int headIndex = -1;
private Config config;
@@ -515,6 +526,24 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
/**
+ * Identifies the tree at the given index as the head tree. This is the tree
+ * use by default to determine attributes and EOL modes.
+ *
+ * @param index
+ * of the tree to use as head
+ * @throws IllegalArgumentException
+ * if the index is out of range
+ * @since 6.1
+ */
+ public void setHead(int index) {
+ if (index < 0 || index >= trees.length) {
+ throw new IllegalArgumentException("Head index " + index //$NON-NLS-1$
+ + " out of range [0," + trees.length + ')'); //$NON-NLS-1$
+ }
+ headIndex = index;
+ }
+
+ /**
* {@inheritDoc}
* <p>
* Retrieve the git attributes for the current entry.
@@ -556,25 +585,51 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
*/
@Override
public Attributes getAttributes() {
- if (attrs != null)
- return attrs;
+ return getAttributes(headIndex);
+ }
+ /**
+ * Retrieves the git attributes based on the given tree.
+ *
+ * @param index
+ * of the tree to use as base for the attributes
+ * @return the attributes
+ * @since 6.1
+ */
+ public Attributes getAttributes(int index) {
+ int attrIndex = index + 1;
+ Attributes result = attrs[attrIndex];
+ if (result != null) {
+ return result;
+ }
if (attributesNodeProvider == null) {
- // The work tree should have a AttributesNodeProvider to be able to
- // retrieve the info and global attributes node
throw new IllegalStateException(
"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
}
try {
- // Lazy create the attributesHandler on the first access of
- // attributes. This requires the info, global and root
- // attributes nodes
- if (attributesHandler == null) {
- attributesHandler = new AttributesHandler(this);
+ AttributesHandler handler = attributesHandlers[attrIndex];
+ if (handler == null) {
+ if (index < 0) {
+ // Legacy behavior (headIndex not set, getAttributes() above
+ // called)
+ handler = new AttributesHandler(this, () -> {
+ return getTree(CanonicalTreeParser.class);
+ });
+ } else {
+ handler = new AttributesHandler(this, () -> {
+ AbstractTreeIterator tree = trees[index];
+ if (tree instanceof CanonicalTreeParser) {
+ return (CanonicalTreeParser) tree;
+ }
+ return null;
+ });
+ }
+ attributesHandlers[attrIndex] = handler;
}
- attrs = attributesHandler.getAttributes();
- return attrs;
+ result = handler.getAttributes();
+ attrs[attrIndex] = result;
+ return result;
} catch (IOException e) {
throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$
e);
@@ -595,11 +650,34 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
*/
@Nullable
public EolStreamType getEolStreamType(OperationType opType) {
- if (attributesNodeProvider == null || config == null)
+ if (attributesNodeProvider == null || config == null) {
return null;
- return EolStreamTypeUtil.detectStreamType(
- opType != null ? opType : operationType,
- config.get(WorkingTreeOptions.KEY), getAttributes());
+ }
+ OperationType op = opType != null ? opType : operationType;
+ return EolStreamTypeUtil.detectStreamType(op,
+ config.get(WorkingTreeOptions.KEY), getAttributes());
+ }
+
+ /**
+ * Get the EOL stream type of the current entry for checking out using the
+ * config and {@link #getAttributes()}.
+ *
+ * @param tree
+ * index of the tree the check-out is to be from
+ * @return the EOL stream type of the current entry using the config and
+ * {@link #getAttributes()}. Note that this method may return null
+ * if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on
+ * a working tree
+ * @since 6.1
+ */
+ @Nullable
+ public EolStreamType getCheckoutEolStreamType(int tree) {
+ if (attributesNodeProvider == null || config == null) {
+ return null;
+ }
+ Attributes attr = getAttributes(tree);
+ return EolStreamTypeUtil.detectStreamType(OperationType.CHECKOUT_OP,
+ config.get(WorkingTreeOptions.KEY), attr);
}
/**
@@ -607,7 +685,8 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
*/
public void reset() {
attrs = null;
- attributesHandler = null;
+ attributesHandlers = null;
+ headIndex = -1;
trees = NO_TREES;
advance = false;
depth = 0;
@@ -651,7 +730,9 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
advance = false;
depth = 0;
- attrs = null;
+ attrs = new Attributes[2];
+ attributesHandlers = new AttributesHandler[2];
+ headIndex = -1;
}
/**
@@ -701,7 +782,14 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
trees = r;
advance = false;
depth = 0;
- attrs = null;
+ if (oldLen == newLen) {
+ Arrays.fill(attrs, null);
+ Arrays.fill(attributesHandlers, null);
+ } else {
+ attrs = new Attributes[newLen + 1];
+ attributesHandlers = new AttributesHandler[newLen + 1];
+ }
+ headIndex = -1;
}
/**
@@ -758,6 +846,16 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
p.matchShift = 0;
trees = newTrees;
+ if (attrs == null) {
+ attrs = new Attributes[n + 2];
+ } else {
+ attrs = Arrays.copyOf(attrs, n + 2);
+ }
+ if (attributesHandlers == null) {
+ attributesHandlers = new AttributesHandler[n + 2];
+ } else {
+ attributesHandlers = Arrays.copyOf(attributesHandlers, n + 2);
+ }
return n;
}
@@ -800,7 +898,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
for (;;) {
- attrs = null;
+ Arrays.fill(attrs, null);
final AbstractTreeIterator t = min();
if (t.eof()) {
if (depth > 0) {
@@ -1255,7 +1353,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
*/
public void enterSubtree() throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
- attrs = null;
+ Arrays.fill(attrs, null);
final AbstractTreeIterator ch = currentHead;
final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
for (int i = 0; i < trees.length; i++) {
@@ -1374,11 +1472,12 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
/**
* Inspect config and attributes to return a filtercommand applicable for
- * the current path, but without expanding %f occurences
+ * the current path.
*
* @param filterCommandType
* which type of filterCommand should be executed. E.g. "clean",
- * "smudge"
+ * "smudge". For "smudge" consider using
+ * {{@link #getSmudgeCommand(int)} instead.
* @return a filter command
* @throws java.io.IOException
* @since 4.2
@@ -1407,6 +1506,54 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
/**
+ * Inspect config and attributes to return a filtercommand applicable for
+ * the current path.
+ *
+ * @param index
+ * of the tree the item to be smudged is in
+ * @return a filter command
+ * @throws java.io.IOException
+ * @since 6.1
+ */
+ public String getSmudgeCommand(int index)
+ throws IOException {
+ return getSmudgeCommand(getAttributes(index));
+ }
+
+ /**
+ * Inspect config and attributes to return a filtercommand applicable for
+ * the current path.
+ *
+ * @param attributes
+ * to use
+ * @return a filter command
+ * @throws java.io.IOException
+ * @since 6.1
+ */
+ public String getSmudgeCommand(Attributes attributes) throws IOException {
+ if (attributes == null) {
+ return null;
+ }
+ Attribute f = attributes.get(Constants.ATTR_FILTER);
+ if (f == null) {
+ return null;
+ }
+ String filterValue = f.getValue();
+ if (filterValue == null) {
+ return null;
+ }
+
+ String filterCommand = getFilterCommandDefinition(filterValue,
+ Constants.ATTR_FILTER_TYPE_SMUDGE);
+ if (filterCommand == null) {
+ return null;
+ }
+ return filterCommand.replaceAll("%f", //$NON-NLS-1$
+ Matcher.quoteReplacement(
+ QuotedString.BOURNE.quote((getPathString()))));
+ }
+
+ /**
* Get the filter command how it is defined in gitconfig. The returned
* string may contain "%f" which needs to be replaced by the current path
* before executing the filter command. These filter definitions are cached
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 50ce15ebc9..427eac5b53 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -2,7 +2,7 @@
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
* Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
- * Copyright (C) 2012-2021, Robin Rosenberg and others
+ * Copyright (C) 2012, 2022, Robin Rosenberg 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
@@ -387,8 +387,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
state.initializeReadBuffer();
final long len = e.getLength();
- InputStream filteredIs = possiblyFilteredInputStream(e, is, len,
- OperationType.CHECKIN_OP);
+ InputStream filteredIs = possiblyFilteredInputStream(e, is,
+ len);
return computeHash(filteredIs, canonLen);
} finally {
safeClose(is);
@@ -400,23 +400,18 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
private InputStream possiblyFilteredInputStream(final Entry e,
- final InputStream is, final long len) throws IOException {
- return possiblyFilteredInputStream(e, is, len, null);
-
- }
-
- private InputStream possiblyFilteredInputStream(final Entry e,
- final InputStream is, final long len, OperationType opType)
+ final InputStream is, final long len)
throws IOException {
if (getCleanFilterCommand() == null
- && getEolStreamType(opType) == EolStreamType.DIRECT) {
+ && getEolStreamType(
+ OperationType.CHECKIN_OP) == EolStreamType.DIRECT) {
canonLen = len;
return is;
}
if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
- rawbuf = filterClean(rawbuf.array(), rawbuf.limit(), opType);
+ rawbuf = filterClean(rawbuf.array(), rawbuf.limit());
canonLen = rawbuf.limit();
return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen);
}
@@ -426,14 +421,13 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
return is;
}
- final InputStream lenIs = filterClean(e.openInputStream(),
- opType);
+ final InputStream lenIs = filterClean(e.openInputStream());
try {
canonLen = computeLength(lenIs);
} finally {
safeClose(lenIs);
}
- return filterClean(is, opType);
+ return filterClean(is);
}
private static void safeClose(InputStream in) {
@@ -455,23 +449,20 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
- private ByteBuffer filterClean(byte[] src, int n, OperationType opType)
+ private ByteBuffer filterClean(byte[] src, int n)
throws IOException {
InputStream in = new ByteArrayInputStream(src);
try {
- return IO.readWholeStream(filterClean(in, opType), n);
+ return IO.readWholeStream(filterClean(in), n);
} finally {
safeClose(in);
}
}
- private InputStream filterClean(InputStream in) throws IOException {
- return filterClean(in, null);
- }
-
- private InputStream filterClean(InputStream in, OperationType opType)
+ private InputStream filterClean(InputStream in)
throws IOException {
- in = handleAutoCRLF(in, opType);
+ in = EolStreamTypeUtil.wrapInputStream(in,
+ getEolStreamType(OperationType.CHECKIN_OP));
String filterCommand = getCleanFilterCommand();
if (filterCommand != null) {
if (FilterCommandRegistry.isRegistered(filterCommand)) {
@@ -509,11 +500,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
return in;
}
- private InputStream handleAutoCRLF(InputStream in, OperationType opType)
- throws IOException {
- return EolStreamTypeUtil.wrapInputStream(in, getEolStreamType(opType));
- }
-
/**
* Returns the working tree options used by this iterator.
*
@@ -664,7 +650,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
public InputStream openEntryStream() throws IOException {
InputStream rawis = current().openInputStream();
if (getCleanFilterCommand() == null
- && getEolStreamType() == EolStreamType.DIRECT) {
+ && getEolStreamType(
+ OperationType.CHECKIN_OP) == EolStreamType.DIRECT) {
return rawis;
}
return filterClean(rawis);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index ff094f6975..ae73d3feb8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -16,6 +16,7 @@ import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
@@ -96,6 +97,9 @@ public class FS_Win32 extends FS {
/** {@inheritDoc} */
@Override
public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
+ if (!Files.isDirectory(directory.toPath(), LinkOption.NOFOLLOW_LINKS)) {
+ return NO_ENTRIES;
+ }
List<Entry> result = new ArrayList<>();
FS fs = this;
boolean checkExecutable = fs.supportsExecute();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
index 8ab13385e0..917add3609 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2010, Google Inc. and others
+ * Copyright (C) 2009-2022, Google Inc. 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
@@ -15,6 +15,7 @@ import java.util.Collection;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
/**
* Miscellaneous string comparison utility methods.
@@ -37,6 +38,10 @@ public final class StringUtils {
LC[c] = (char) ('a' + (c - 'A'));
}
+ private StringUtils() {
+ // Do not create instances
+ }
+
/**
* Convert the input to lowercase.
* <p>
@@ -269,8 +274,20 @@ public final class StringUtils {
return sb.toString();
}
- private StringUtils() {
- // Do not create instances
+ /**
+ * Appends {@link Constants#DOT_GIT_EXT} unless the given name already ends
+ * with that suffix.
+ *
+ * @param name
+ * to complete
+ * @return the name ending with {@link Constants#DOT_GIT_EXT}
+ * @since 6.1
+ */
+ public static String nameWithDotGit(String name) {
+ if (name.endsWith(Constants.DOT_GIT_EXT)) {
+ return name;
+ }
+ return name + Constants.DOT_GIT_EXT;
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java
index 4f940d77a0..2c972b578d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * 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
@@ -90,7 +90,7 @@ public class BinaryHunkInputStream extends InputStream {
byte[] encoded = new byte[Base85.encodedLength(length)];
for (int i = 0; i < encoded.length; i++) {
int b = in.read();
- if (b < 0 || b == '\n') {
+ if (b < 0 || b == '\r' || b == '\n') {
throw new EOFException(MessageFormat.format(
JGitText.get().binaryHunkInvalidLength,
Integer.valueOf(lineNumber)));
@@ -99,6 +99,10 @@ public class BinaryHunkInputStream extends InputStream {
}
// Must be followed by a newline; tolerate EOF.
int b = in.read();
+ if (b == '\r') {
+ // Be lenient and accept CR-LF, too.
+ b = in.read();
+ }
if (b >= 0 && b != '\n') {
throw new StreamCorruptedException(MessageFormat.format(
JGitText.get().binaryHunkMissingNewline,