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_filters74
-rw-r--r--org.eclipse.jgit/BUILD6
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF106
-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.properties7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java83
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java92
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java133
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java195
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java136
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java80
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java1210
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java40
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java159
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java60
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java56
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java49
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java88
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java104
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java60
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java70
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java17
56 files changed, 2464 insertions, 912 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 00b89a4b3d..1683f33d0c 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,77 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.jgit" version="2">
- <resource path="src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java" type="org.eclipse.jgit.errors.NoRemoteRepositoryException">
- <filter id="1141899266">
+ <resource path="src/org/eclipse/jgit/transport/AwsRequestSignerV4.java" type="org.eclipse.jgit.transport.AwsRequestSignerV4">
+ <filter id="1108344834">
<message_arguments>
<message_argument value="5.13"/>
- <message_argument value="6.1"/>
- <message_argument value="NoRemoteRepositoryException(URIish, String, Throwable)"/>
- </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/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/TypedConfigGetter.java" type="org.eclipse.jgit.lib.TypedConfigGetter">
- <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_argument value="6.3"/>
+ <message_argument value="org.eclipse.jgit.transport.AwsRequestSignerV4"/>
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/transport/BasePackPushConnection.java" type="org.eclipse.jgit.transport.BasePackPushConnection">
- <filter id="338792546">
+ <resource path="src/org/eclipse/jgit/util/HttpSupport.java" type="org.eclipse.jgit.util.HttpSupport">
+ <filter id="1141899266">
<message_arguments>
- <message_argument value="org.eclipse.jgit.transport.BasePackPushConnection"/>
- <message_argument value="noRepository()"/>
+ <message_argument value="5.13"/>
+ <message_argument value="6.3"/>
+ <message_argument value="urlEncode(String, boolean)"/>
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/transport/PushConfig.java" type="org.eclipse.jgit.transport.PushConfig">
- <filter id="338722907">
+ <resource path="src/org/eclipse/jgit/util/Paths.java" type="org.eclipse.jgit.util.Paths">
+ <filter id="337768515">
<message_arguments>
- <message_argument value="org.eclipse.jgit.transport.PushConfig"/>
- <message_argument value="PushConfig()"/>
+ <message_argument value="org.eclipse.jgit.util.Paths"/>
</message_arguments>
</filter>
</resource>
diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD
index 04873b0c72..e806e7d6d0 100644
--- a/org.eclipse.jgit/BUILD
+++ b/org.eclipse.jgit/BUILD
@@ -23,6 +23,12 @@ java_library(
"//lib:javaewah",
"//lib:slf4j-api",
],
+ javacopts = [
+ "-Xep:ReferenceEquality:OFF",
+ "-Xep:StringEquality:OFF",
+ "-Xep:TypeParameterUnusedInFormals:OFF",
+ "-Xep:DefaultCharset:OFF",
+ ]
)
genrule(
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 1b34912915..307a8016e3 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.2.1.qualifier
+Bundle-Version: 6.3.1.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %Bundle-Vendor
Eclipse-ExtensibleAPI: true
-Export-Package: org.eclipse.jgit.annotations;version="6.2.1",
- org.eclipse.jgit.api;version="6.2.1";
+Export-Package: org.eclipse.jgit.annotations;version="6.3.1",
+ org.eclipse.jgit.api;version="6.3.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.2.1",
org.eclipse.jgit.revwalk.filter,
org.eclipse.jgit.blame,
org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="6.2.1";
+ org.eclipse.jgit.api.errors;version="6.3.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="6.2.1";
+ org.eclipse.jgit.attributes;version="6.3.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.blame;version="6.2.1";
+ org.eclipse.jgit.blame;version="6.3.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.2.1";
+ org.eclipse.jgit.diff;version="6.3.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.attributes,
org.eclipse.jgit.revwalk,
@@ -42,49 +42,51 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.1",
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="6.2.1";
+ org.eclipse.jgit.dircache;version="6.3.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.2.1";
+ org.eclipse.jgit.errors;version="6.3.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.2.1";
+ org.eclipse.jgit.events;version="6.3.1";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="6.2.1",
- org.eclipse.jgit.gitrepo;version="6.2.1";
+ org.eclipse.jgit.fnmatch;version="6.3.1",
+ org.eclipse.jgit.gitrepo;version="6.3.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.2.1";x-internal:=true,
- org.eclipse.jgit.hooks;version="6.2.1";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="6.2.1",
- org.eclipse.jgit.ignore.internal;version="6.2.1";
+ org.eclipse.jgit.gitrepo.internal;version="6.3.1";x-internal:=true,
+ org.eclipse.jgit.hooks;version="6.3.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="6.3.1",
+ org.eclipse.jgit.ignore.internal;version="6.3.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="6.2.1";
+ org.eclipse.jgit.internal;version="6.3.1";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.diffmergetool;version="6.2.1";
+ org.eclipse.jgit.internal.diff;version="6.3.1";
+ x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.diffmergetool;version="6.3.1";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.pgm.test,
org.eclipse.jgit.pgm,
org.eclipse.egit.ui",
- org.eclipse.jgit.internal.fsck;version="6.2.1";
+ org.eclipse.jgit.internal.fsck;version="6.3.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.revwalk;version="6.2.1";
+ org.eclipse.jgit.internal.revwalk;version="6.3.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.dfs;version="6.2.1";
+ org.eclipse.jgit.internal.storage.dfs;version="6.3.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.2.1";
+ org.eclipse.jgit.internal.storage.file;version="6.3.1";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.junit.http,
@@ -93,32 +95,32 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.1",
org.eclipse.jgit.pgm,
org.eclipse.jgit.pgm.test,
org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.internal.storage.io;version="6.2.1";
+ org.eclipse.jgit.internal.storage.io;version="6.3.1";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="6.2.1";
+ org.eclipse.jgit.internal.storage.pack;version="6.3.1";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="6.2.1";
+ org.eclipse.jgit.internal.storage.reftable;version="6.3.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.2.1";x-internal:=true,
- org.eclipse.jgit.internal.transport.connectivity;version="6.2.1";
+ org.eclipse.jgit.internal.submodule;version="6.3.1";x-internal:=true,
+ org.eclipse.jgit.internal.transport.connectivity;version="6.3.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.http;version="6.2.1";
+ org.eclipse.jgit.internal.transport.http;version="6.3.1";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.parser;version="6.2.1";
+ org.eclipse.jgit.internal.transport.parser;version="6.3.1";
x-friends:="org.eclipse.jgit.http.server,
org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="6.2.1";
+ org.eclipse.jgit.internal.transport.ssh;version="6.3.1";
x-friends:="org.eclipse.jgit.ssh.apache,
org.eclipse.jgit.ssh.jsch,
org.eclipse.jgit.test",
- org.eclipse.jgit.lib;version="6.2.1";
+ org.eclipse.jgit.lib;version="6.3.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.util.sha1,
org.eclipse.jgit.dircache,
@@ -132,12 +134,12 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.1",
org.eclipse.jgit.util,
org.eclipse.jgit.submodule,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.lib.internal;version="6.2.1";
+ org.eclipse.jgit.lib.internal;version="6.3.1";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.pgm,
org.eclipse.egit.ui",
- org.eclipse.jgit.logging;version="6.2.1",
- org.eclipse.jgit.merge;version="6.2.1";
+ org.eclipse.jgit.logging;version="6.3.1",
+ org.eclipse.jgit.merge;version="6.3.1";
uses:="org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
@@ -146,40 +148,40 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.1",
org.eclipse.jgit.util,
org.eclipse.jgit.api,
org.eclipse.jgit.attributes",
- org.eclipse.jgit.nls;version="6.2.1",
- org.eclipse.jgit.notes;version="6.2.1";
+ org.eclipse.jgit.nls;version="6.3.1",
+ org.eclipse.jgit.notes;version="6.3.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="6.2.1";
+ org.eclipse.jgit.patch;version="6.3.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="6.2.1";
+ org.eclipse.jgit.revplot;version="6.3.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="6.2.1";
+ org.eclipse.jgit.revwalk;version="6.3.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.2.1";
+ org.eclipse.jgit.revwalk.filter;version="6.3.1";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="6.2.1";
+ org.eclipse.jgit.storage.file;version="6.3.1";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="6.2.1";
+ org.eclipse.jgit.storage.pack;version="6.3.1";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="6.2.1";
+ org.eclipse.jgit.submodule;version="6.3.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.2.1";
+ org.eclipse.jgit.transport;version="6.3.1";
uses:="javax.crypto,
org.eclipse.jgit.util.io,
org.eclipse.jgit.lib,
@@ -192,21 +194,21 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.1",
org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.storage.pack,
org.eclipse.jgit.errors",
- org.eclipse.jgit.transport.http;version="6.2.1";
+ org.eclipse.jgit.transport.http;version="6.3.1";
uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="6.2.1";
+ org.eclipse.jgit.transport.resolver;version="6.3.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.lib",
- org.eclipse.jgit.treewalk;version="6.2.1";
+ org.eclipse.jgit.treewalk;version="6.3.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.2.1";
+ org.eclipse.jgit.treewalk.filter;version="6.3.1";
uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="6.2.1";
+ org.eclipse.jgit.util;version="6.3.1";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.hooks,
org.eclipse.jgit.revwalk,
@@ -219,12 +221,12 @@ Export-Package: org.eclipse.jgit.annotations;version="6.2.1",
org.eclipse.jgit.treewalk,
javax.net.ssl,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.util.io;version="6.2.1";
+ org.eclipse.jgit.util.io;version="6.3.1";
uses:="org.eclipse.jgit.attributes,
org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util.sha1;version="6.2.1",
- org.eclipse.jgit.util.time;version="6.2.1"
+ org.eclipse.jgit.util.sha1;version="6.3.1",
+ org.eclipse.jgit.util.time;version="6.3.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 4066aea51a..43e88e531c 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.2.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="6.2.1.qualifier";roots="."
+Bundle-Version: 6.3.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="6.3.1.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index e2236440a2..7e6bc0a4f4 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.2.1-SNAPSHOT</version>
+ <version>6.3.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 66adad5151..f3ecadd6e5 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -38,7 +38,7 @@ badIgnorePatternFull=File {0} line {1}: cannot parse pattern ''{2}'': {3}
badObjectType=Bad object type: {0}
badRef=Bad ref: {0}: {1}
badSectionEntry=Bad section entry: {0}
-badShallowLine=Bad shallow line: {0}
+badShallowLine=Shallow file ''{0}'' has bad line: {1}
bareRepositoryNoWorkdirAndIndex=Bare Repository has neither a working tree, nor an index
base85invalidChar=Invalid base-85 character: 0x{0}
base85length=Base-85 encoded data must have a length that is a multiple of 5
@@ -237,6 +237,8 @@ deletedOrphanInPackDir=Deleted orphaned file {}
deleteRequiresZeroNewId=Delete requires new ID to be zero
deleteTagUnexpectedResult=Delete tag returned unexpected result {0}
deletingNotSupported=Deleting {0} not supported.
+depthMustBeAt1=Depth must be >= 1
+depthWithUnshallow=Depth and unshallow can\'t be used together
destinationIsNotAWildcard=Destination is not a wildcard.
detachedHeadDetected=HEAD is detached
diffToolNotGivenError=No diff tool provided and no defaults configured.
@@ -518,6 +520,7 @@ notFound=not found.
nothingToFetch=Nothing to fetch.
nothingToPush=Nothing to push.
notMergedExceptionMessage=Branch was not deleted as it has not been merged yet; use the force option to delete it anyway
+notShallowedUnshallow=The server sent a unshallow for a commit that wasn''t marked as shallow: {0}
noXMLParserAvailable=No XML parser available.
objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream
objectIsCorrupt=Object {0} is corrupt: {1}
@@ -587,6 +590,7 @@ pushNotPermitted=push not permitted
pushOptionsNotSupported=Push options not supported; received {0}
rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry
readConfigFailed=Reading config file ''{0}'' failed
+readShallowFailed=Reading shallow file ''{0}'' failed
readFileStoreAttributesFailed=Reading FileStore attributes from user config failed
readerIsRequired=Reader is required
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
@@ -662,6 +666,7 @@ serviceNotEnabledNoName=Service not enabled
serviceNotPermitted={1} not permitted on ''{0}''
sha1CollisionDetected=SHA-1 collision detected on {0}
shallowCommitsAlreadyInitialized=Shallow commits have already been initialized
+shallowNotSupported=The server does not support shallow
shallowPacksRequireDepthWalk=Shallow packs require a DepthWalk
shortCompressedStreamAt=Short compressed stream at {0}
shortReadOfBlock=Short read of block.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index 583767af3f..e7f40d811b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -9,7 +9,6 @@
*/
package org.eclipse.jgit.api;
-import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -25,7 +24,6 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.InflaterInputStream;
-
import org.eclipse.jgit.api.errors.FilterFailedException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.PatchApplyException;
@@ -38,15 +36,11 @@ import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
import org.eclipse.jgit.dircache.DirCacheIterator;
-import org.eclipse.jgit.errors.LargeObjectException;
-import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.BinaryHunk;
import org.eclipse.jgit.patch.FileHeader;
@@ -162,13 +156,14 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
JGitText.get().renameFileFailed, f, dest), e);
}
apply(repository, fh.getOldPath(), cache, dest, fh);
+ r.addUpdatedFile(dest);
break;
case COPY:
- f = getFile(fh.getOldPath(), false);
- File target = getFile(fh.getNewPath(), false);
- FileUtils.mkdirs(target.getParentFile(), true);
- Files.copy(f.toPath(), target.toPath());
- apply(repository, fh.getOldPath(), cache, target, fh);
+ File src = getFile(fh.getOldPath(), false);
+ f = getFile(fh.getNewPath(), false);
+ FileUtils.mkdirs(f.getParentFile(), true);
+ Files.copy(src.toPath(), f.toPath());
+ apply(repository, fh.getOldPath(), cache, f, fh);
}
r.addUpdatedFile(f);
}
@@ -355,60 +350,6 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
return result.getStdout().openInputStreamWithAutoDestroy();
}
- /**
- * Something that can supply an {@link InputStream}.
- */
- private interface StreamSupplier {
- InputStream load() throws IOException;
- }
-
- /**
- * We write the patch result to a {@link TemporaryBuffer} and then use
- * {@link DirCacheCheckout}.getContent() to run the result through the CR-LF
- * and smudge filters. DirCacheCheckout needs an ObjectLoader, not a
- * TemporaryBuffer, so this class bridges between the two, making any Stream
- * provided by a {@link StreamSupplier} look like an ordinary git blob to
- * DirCacheCheckout.
- */
- private static class StreamLoader extends ObjectLoader {
-
- private StreamSupplier data;
-
- private long size;
-
- StreamLoader(StreamSupplier data, long length) {
- this.data = data;
- this.size = length;
- }
-
- @Override
- public int getType() {
- return Constants.OBJ_BLOB;
- }
-
- @Override
- public long getSize() {
- return size;
- }
-
- @Override
- public boolean isLarge() {
- return true;
- }
-
- @Override
- public byte[] getCachedBytes() throws LargeObjectException {
- throw new LargeObjectException();
- }
-
- @Override
- public ObjectStream openStream()
- throws MissingObjectException, IOException {
- return new ObjectStream.Filter(getType(), getSize(),
- new BufferedInputStream(data.load()));
- }
- }
-
private void initHash(SHA1 hash, long size) {
hash.update(Constants.encodedTypeString(Constants.OBJ_BLOB));
hash.update((byte) ' ');
@@ -456,7 +397,7 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
}
private void applyBinary(Repository repository, String path, File f,
- FileHeader fh, StreamSupplier loader, ObjectId id,
+ FileHeader fh, DirCacheCheckout.StreamSupplier loader, ObjectId id,
CheckoutMetadata checkOut)
throws PatchApplyException, IOException {
if (!fh.getOldId().isComplete() || !fh.getNewId().isComplete()) {
@@ -488,8 +429,7 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
hunk.getBuffer(), start,
length))))) {
DirCacheCheckout.getContent(repository, path, checkOut,
- new StreamLoader(() -> inflated, hunk.getSize()),
- null, out);
+ () -> inflated, null, out);
if (!fh.getNewId().toObjectId().equals(hash.toObjectId())) {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().applyBinaryResultOidWrong,
@@ -520,8 +460,7 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
SHA1InputStream hashed = new SHA1InputStream(hash,
input)) {
DirCacheCheckout.getContent(repository, path, checkOut,
- new StreamLoader(() -> hashed, finalSize), null,
- out);
+ () -> hashed, null, out);
if (!fh.getNewId().toObjectId()
.equals(hash.toObjectId())) {
throw new PatchApplyException(MessageFormat.format(
@@ -689,9 +628,7 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
}
try (OutputStream output = new FileOutputStream(f)) {
DirCacheCheckout.getContent(repository, path, checkOut,
- new StreamLoader(buffer::openInputStream,
- buffer.length()),
- null, output);
+ buffer::openInputStream, null, output);
}
} finally {
buffer.destroy();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index 847ab0a9a8..7319ff4b2f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -55,6 +55,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
/**
@@ -411,6 +412,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
protected CheckoutCommand checkoutPaths() throws IOException,
RefNotFoundException {
actuallyModifiedPaths = new HashSet<>();
+ WorkingTreeOptions options = repo.getConfig()
+ .get(WorkingTreeOptions.KEY);
DirCache dc = repo.lockDirCache();
try (RevWalk revWalk = new RevWalk(repo);
TreeWalk treeWalk = new TreeWalk(repo,
@@ -419,10 +422,10 @@ public class CheckoutCommand extends GitCommand<Ref> {
if (!checkoutAllPaths)
treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
if (isCheckoutIndex())
- checkoutPathsFromIndex(treeWalk, dc);
+ checkoutPathsFromIndex(treeWalk, dc, options);
else {
RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
- checkoutPathsFromCommit(treeWalk, dc, commit);
+ checkoutPathsFromCommit(treeWalk, dc, commit, options);
}
} finally {
try {
@@ -439,7 +442,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
return this;
}
- private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc)
+ private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc,
+ WorkingTreeOptions options)
throws IOException {
DirCacheIterator dci = new DirCacheIterator(dc);
treeWalk.addTree(dci);
@@ -465,8 +469,9 @@ public class CheckoutCommand extends GitCommand<Ref> {
if (stage > DirCacheEntry.STAGE_0) {
if (checkoutStage != null) {
if (stage == checkoutStage.number) {
- checkoutPath(ent, r, new CheckoutMetadata(
- eolStreamType, filterCommand));
+ checkoutPath(ent, r, options,
+ new CheckoutMetadata(eolStreamType,
+ filterCommand));
actuallyModifiedPaths.add(path);
}
} else {
@@ -475,8 +480,9 @@ public class CheckoutCommand extends GitCommand<Ref> {
throw new JGitInternalException(e.getMessage(), e);
}
} else {
- checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
- filterCommand));
+ checkoutPath(ent, r, options,
+ new CheckoutMetadata(eolStreamType,
+ filterCommand));
actuallyModifiedPaths.add(path);
}
}
@@ -488,7 +494,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
- RevCommit commit) throws IOException {
+ RevCommit commit, WorkingTreeOptions options) throws IOException {
treeWalk.addTree(commit.getTree());
final ObjectReader r = treeWalk.getObjectReader();
DirCacheEditor editor = dc.editor();
@@ -510,7 +516,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
ent.setObjectId(blobId);
ent.setFileMode(mode);
- checkoutPath(ent, r,
+ checkoutPath(ent, r, options,
new CheckoutMetadata(eolStreamType, filterCommand));
actuallyModifiedPaths.add(path);
}
@@ -520,10 +526,10 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
- CheckoutMetadata checkoutMetadata) {
+ WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
try {
DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
- checkoutMetadata);
+ checkoutMetadata, options);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
index 69272b7547..36ca97d694 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
@@ -25,6 +25,7 @@ import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.Paths;
/**
* Remove untracked files from the working tree
@@ -91,15 +92,16 @@ public class CleanCommand extends GitCommand<Set<String>> {
Set<String> notIgnoredDirs = filterIgnorePaths(untrackedDirs,
status.getIgnoredNotInIndex(), false);
- for (String file : notIgnoredFiles)
+ for (String file : notIgnoredFiles) {
if (paths.isEmpty() || paths.contains(file)) {
files = cleanPath(file, files);
}
-
- for (String dir : notIgnoredDirs)
+ }
+ for (String dir : notIgnoredDirs) {
if (paths.isEmpty() || paths.contains(dir)) {
files = cleanPath(dir, files);
}
+ }
} catch (IOException e) {
throw new JGitInternalException(e.getMessage(), e);
} finally {
@@ -142,14 +144,14 @@ public class CleanCommand extends GitCommand<Set<String>> {
FileUtils.delete(curFile, FileUtils.RECURSIVE
| FileUtils.SKIP_MISSING);
}
- inFiles.add(path + "/"); //$NON-NLS-1$
+ inFiles.add(path + '/');
}
} else {
if (!dryRun) {
FileUtils.delete(curFile,
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
}
- inFiles.add(path + "/"); //$NON-NLS-1$
+ inFiles.add(path + '/');
}
}
} else {
@@ -166,14 +168,16 @@ public class CleanCommand extends GitCommand<Set<String>> {
Set<String> ignoredNotInIndex, boolean exact) {
if (ignore) {
Set<String> filtered = new TreeSet<>(inputPaths);
- for (String path : inputPaths)
- for (String ignored : ignoredNotInIndex)
+ for (String path : inputPaths) {
+ for (String ignored : ignoredNotInIndex) {
if ((exact && path.equals(ignored))
- || (!exact && path.startsWith(ignored))) {
+ || (!exact
+ && Paths.isEqualOrPrefix(ignored, path))) {
filtered.remove(path);
break;
}
-
+ }
+ }
return filtered;
}
return inputPaths;
@@ -182,14 +186,14 @@ public class CleanCommand extends GitCommand<Set<String>> {
private Set<String> filterFolders(Set<String> untracked,
Set<String> untrackedFolders) {
Set<String> filtered = new TreeSet<>(untracked);
- for (String file : untracked)
- for (String folder : untrackedFolders)
- if (file.startsWith(folder)) {
+ for (String file : untracked) {
+ for (String folder : untrackedFolders) {
+ if (Paths.isEqualOrPrefix(folder, file)) {
filtered.remove(file);
break;
}
-
-
+ }
+ }
return filtered;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index 3aa711455b..1f979a938c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, 2017 Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * Copyright (C) 2011, 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
@@ -13,10 +13,13 @@ import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
@@ -91,6 +94,12 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private TagOpt tagOption;
+ private Integer depth;
+
+ private Instant shallowSince;
+
+ private List<String> shallowExcludes = new ArrayList<>();
+
private enum FETCH_TYPE {
MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR
}
@@ -306,6 +315,11 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW);
}
command.setInitialBranch(branch);
+ if (depth != null) {
+ command.setDepth(depth.intValue());
+ }
+ command.setShallowSince(shallowSince);
+ command.setShallowExcludes(shallowExcludes);
configure(command);
return command.call();
@@ -737,6 +751,82 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
return this;
}
+ /**
+ * Creates a shallow clone with a history truncated to the specified number
+ * of commits.
+ *
+ * @param depth
+ * the depth
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public CloneCommand setDepth(int depth) {
+ if (depth < 1) {
+ throw new IllegalArgumentException(JGitText.get().depthMustBeAt1);
+ }
+ this.depth = Integer.valueOf(depth);
+ return this;
+ }
+
+ /**
+ * Creates a shallow clone with a history after the specified time.
+ *
+ * @param shallowSince
+ * the timestammp; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public CloneCommand setShallowSince(@NonNull OffsetDateTime shallowSince) {
+ this.shallowSince = shallowSince.toInstant();
+ return this;
+ }
+
+ /**
+ * Creates a shallow clone with a history after the specified time.
+ *
+ * @param shallowSince
+ * the timestammp; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public CloneCommand setShallowSince(@NonNull Instant shallowSince) {
+ this.shallowSince = shallowSince;
+ return this;
+ }
+
+ /**
+ * Creates a shallow clone with a history, excluding commits reachable from
+ * a specified remote branch or tag.
+ *
+ * @param shallowExclude
+ * the ref or commit; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public CloneCommand addShallowExclude(@NonNull String shallowExclude) {
+ shallowExcludes.add(shallowExclude);
+ return this;
+ }
+
+ /**
+ * Creates a shallow clone with a history, excluding commits reachable from
+ * a specified remote branch or tag.
+ *
+ * @param shallowExclude
+ * the commit; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public CloneCommand addShallowExclude(@NonNull ObjectId shallowExclude) {
+ shallowExcludes.add(shallowExclude.name());
+ return this;
+ }
+
private static void validateDirs(File directory, File gitDir, boolean bare)
throws IllegalStateException {
if (directory != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index 90c1515b06..84bee36204 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.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
@@ -14,10 +14,13 @@ import static java.util.stream.Collectors.toList;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidConfigurationException;
@@ -76,6 +79,14 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
private String initialBranch;
+ private Integer depth;
+
+ private Instant deepenSince;
+
+ private List<String> shallowExcludes = new ArrayList<>();
+
+ private boolean unshallow;
+
/**
* Callback for status of fetch operation.
*
@@ -156,11 +167,9 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
walk.getPath());
// When the fetch mode is "yes" we always fetch. When the
- // mode
- // is "on demand", we only fetch if the submodule's revision
- // was
- // updated to an object that is not currently present in the
- // submodule.
+ // mode is "on demand", we only fetch if the submodule's
+ // revision was updated to an object that is not currently
+ // present in the submodule.
if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND
&& !submoduleRepo.getObjectDatabase()
.has(walk.getObjectId()))
@@ -209,6 +218,17 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
if (tagOption != null)
transport.setTagOpt(tagOption);
transport.setFetchThin(thin);
+ if (depth != null) {
+ transport.setDepth(depth);
+ }
+ if (unshallow) {
+ if (depth != null) {
+ throw new IllegalStateException(JGitText.get().depthWithUnshallow);
+ }
+ transport.setDepth(Constants.INFINITE_DEPTH);
+ }
+ transport.setDeepenSince(deepenSince);
+ transport.setDeepenNots(shallowExcludes);
configure(transport);
FetchResult result = transport.fetch(monitor,
applyOptions(refSpecs), initialBranch);
@@ -542,4 +562,105 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
this.isForceUpdate = force;
return this;
}
+
+ /**
+ * Limits fetching to the specified number of commits from the tip of each
+ * remote branch history.
+ *
+ * @param depth
+ * the depth
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public FetchCommand setDepth(int depth) {
+ if (depth < 1) {
+ throw new IllegalArgumentException(JGitText.get().depthMustBeAt1);
+ }
+ this.depth = Integer.valueOf(depth);
+ return this;
+ }
+
+ /**
+ * Deepens or shortens the history of a shallow repository to include all
+ * reachable commits after a specified time.
+ *
+ * @param shallowSince
+ * the timestammp; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public FetchCommand setShallowSince(@NonNull OffsetDateTime shallowSince) {
+ this.deepenSince = shallowSince.toInstant();
+ return this;
+ }
+
+ /**
+ * Deepens or shortens the history of a shallow repository to include all
+ * reachable commits after a specified time.
+ *
+ * @param shallowSince
+ * the timestammp; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public FetchCommand setShallowSince(@NonNull Instant shallowSince) {
+ this.deepenSince = shallowSince;
+ return this;
+ }
+
+ /**
+ * Deepens or shortens the history of a shallow repository to exclude
+ * commits reachable from a specified remote branch or tag.
+ *
+ * @param shallowExclude
+ * the ref or commit; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public FetchCommand addShallowExclude(@NonNull String shallowExclude) {
+ shallowExcludes.add(shallowExclude);
+ return this;
+ }
+
+ /**
+ * Creates a shallow clone with a history, excluding commits reachable from
+ * a specified remote branch or tag.
+ *
+ * @param shallowExclude
+ * the commit; must not be {@code null}
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public FetchCommand addShallowExclude(@NonNull ObjectId shallowExclude) {
+ shallowExcludes.add(shallowExclude.name());
+ return this;
+ }
+
+ /**
+ * If the source repository is complete, converts a shallow repository to a
+ * complete one, removing all the limitations imposed by shallow
+ * repositories.
+ *
+ * If the source repository is shallow, fetches as much as possible so that
+ * the current repository has the same history as the source repository.
+ *
+ * @param unshallow
+ * whether to unshallow or not
+ * @return {@code this}
+ *
+ * @since 6.3
+ */
+ public FetchCommand setUnshallow(boolean unshallow) {
+ this.unshallow = unshallow;
+ return this;
+ }
+
+ void setShallowExcludes(List<String> shallowExcludes) {
+ this.shallowExcludes = shallowExcludes;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 1004d3e50f..17036a9cd3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -48,6 +48,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
/**
* Command class to apply a stashed commit.
@@ -382,6 +383,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
private void resetUntracked(RevTree tree) throws CheckoutConflictException,
IOException {
Set<String> actuallyModifiedPaths = new HashSet<>();
+ WorkingTreeOptions options = repo.getConfig()
+ .get(WorkingTreeOptions.KEY);
// TODO maybe NameConflictTreeWalk ?
try (TreeWalk walk = new TreeWalk(repo)) {
walk.addTree(tree);
@@ -413,7 +416,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
}
}
- checkoutPath(entry, reader,
+ checkoutPath(entry, reader, options,
new CheckoutMetadata(eolStreamType, null));
actuallyModifiedPaths.add(entry.getPathString());
}
@@ -426,10 +429,10 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
}
private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
- CheckoutMetadata checkoutMetadata) {
+ WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
try {
DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
- checkoutMetadata);
+ checkoutMetadata, options);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
index 10d77528f6..77967df2e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
@@ -41,6 +41,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.diff.FilteredRenameDetector;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
@@ -1109,9 +1110,10 @@ public class BlameGenerator implements AutoCloseable {
treeWalk.setFilter(TreeFilter.ANY_DIFF);
treeWalk.reset(parent.getTree(), commit.getTree());
- renameDetector.reset();
- renameDetector.addAll(DiffEntry.scan(treeWalk));
- for (DiffEntry ent : renameDetector.compute()) {
+ List<DiffEntry> diffs = DiffEntry.scan(treeWalk);
+ FilteredRenameDetector filteredRenameDetector = new FilteredRenameDetector(
+ renameDetector);
+ for (DiffEntry ent : filteredRenameDetector.compute(diffs, path)) {
if (isRename(ent) && ent.getNewPath().equals(path.getPath()))
return ent;
}
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 f6fc393c45..1fb81b71e9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -21,6 +21,7 @@ import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
@@ -143,6 +144,8 @@ public class DirCacheCheckout {
private boolean performingCheckout;
+ private WorkingTreeOptions options;
+
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
/**
@@ -362,9 +365,12 @@ public class DirCacheCheckout {
* Processing an entry in the context of {@link #prescanOneTree()} when only
* one tree is given
*
- * @param m the tree to merge
- * @param i the index
- * @param f the working tree
+ * @param m
+ * the tree to merge
+ * @param i
+ * the index
+ * @param f
+ * the working tree
* @throws IOException
*/
void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
@@ -489,6 +495,8 @@ public class DirCacheCheckout {
MissingObjectException, IncorrectObjectTypeException,
CheckoutConflictException, IndexWriteException, CanceledException {
toBeDeleted.clear();
+ options = repo.getConfig()
+ .get(WorkingTreeOptions.KEY);
try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
if (headCommitTree != null)
preScanTwoTrees();
@@ -558,7 +566,8 @@ public class DirCacheCheckout {
if (FileMode.GITLINK.equals(entry.getRawMode())) {
checkoutGitlink(path, entry);
} else {
- checkoutEntry(repo, entry, objectReader, false, meta);
+ checkoutEntry(repo, entry, objectReader, false, meta,
+ options);
}
e = null;
@@ -594,7 +603,7 @@ public class DirCacheCheckout {
}
if (entry.getStage() == DirCacheEntry.STAGE_3) {
checkoutEntry(repo, entry, objectReader, false,
- null);
+ null, options);
break;
}
++entryIdx;
@@ -1226,7 +1235,7 @@ public class DirCacheCheckout {
checkoutEntry(repo, e, walk.getObjectReader(), false,
new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP),
walk.getFilterCommand(
- Constants.ATTR_FILTER_TYPE_SMUDGE)));
+ Constants.ATTR_FILTER_TYPE_SMUDGE)), options);
}
}
}
@@ -1392,12 +1401,6 @@ public class DirCacheCheckout {
* cannot be renamed to file or link without deleting it recursively.
* </p>
*
- * <p>
- * TODO: this method works directly on File IO, we may need another
- * abstraction (like WorkingTreeIterator). This way we could tell e.g.
- * Eclipse that Files in the workspace got changed
- * </p>
- *
* @param repo
* repository managing the destination work tree.
* @param entry
@@ -1407,15 +1410,16 @@ public class DirCacheCheckout {
* @throws java.io.IOException
* @since 3.6
* @deprecated since 5.1, use
- * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata)}
+ * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)}
* instead
*/
@Deprecated
public static void checkoutEntry(Repository repo, DirCacheEntry entry,
ObjectReader or) throws IOException {
- checkoutEntry(repo, entry, or, false, null);
+ checkoutEntry(repo, entry, or, false, null, null);
}
+
/**
* Updates the file in the working tree with content and mode from an entry
* in the index. The new content is first written to a new temporary file in
@@ -1428,10 +1432,45 @@ public class DirCacheCheckout {
* recursively, independently if has any content.
* </p>
*
+ * @param repo
+ * repository managing the destination work tree.
+ * @param entry
+ * the entry containing new mode and content
+ * @param or
+ * object reader to use for checkout
+ * @param deleteRecursive
+ * true to recursively delete final path if it exists on the file
+ * system
+ * @param checkoutMetadata
+ * containing
+ * <ul>
+ * <li>smudgeFilterCommand to be run for smudging the entry to be
+ * checked out</li>
+ * <li>eolStreamType used for stream conversion</li>
+ * </ul>
+ * @throws java.io.IOException
+ * @since 4.2
+ * @deprecated since 6.3, use
+ * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)}
+ * instead
+ */
+ @Deprecated
+ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
+ ObjectReader or, boolean deleteRecursive,
+ CheckoutMetadata checkoutMetadata) throws IOException {
+ checkoutEntry(repo, entry, or, deleteRecursive, checkoutMetadata, null);
+ }
+
+ /**
+ * Updates the file in the working tree with content and mode from an entry
+ * in the index. The new content is first written to a new temporary file in
+ * the same directory as the real file. Then that new file is renamed to the
+ * final filename.
+ *
* <p>
- * TODO: this method works directly on File IO, we may need another
- * abstraction (like WorkingTreeIterator). This way we could tell e.g.
- * Eclipse that Files in the workspace got changed
+ * <b>Note:</b> if the entry path on local file system exists as a file, it
+ * will be deleted and if it exists as a directory, it will be deleted
+ * recursively, independently if has any content.
* </p>
*
* @param repo
@@ -1450,14 +1489,19 @@ public class DirCacheCheckout {
* checked out</li>
* <li>eolStreamType used for stream conversion</li>
* </ul>
+ * @param options
+ * {@link WorkingTreeOptions} that are effective; if {@code null}
+ * they are loaded from the repository config
* @throws java.io.IOException
- * @since 4.2
+ * @since 6.3
*/
public static void checkoutEntry(Repository repo, DirCacheEntry entry,
ObjectReader or, boolean deleteRecursive,
- CheckoutMetadata checkoutMetadata) throws IOException {
- if (checkoutMetadata == null)
+ CheckoutMetadata checkoutMetadata, WorkingTreeOptions options)
+ throws IOException {
+ if (checkoutMetadata == null) {
checkoutMetadata = CheckoutMetadata.EMPTY;
+ }
ObjectLoader ol = or.open(entry.getObjectId());
File f = new File(repo.getWorkTree(), entry.getPathString());
File parentDir = f.getParentFile();
@@ -1466,7 +1510,8 @@ public class DirCacheCheckout {
}
FileUtils.mkdirs(parentDir, true);
FS fs = repo.getFS();
- WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
+ WorkingTreeOptions opt = options != null ? options
+ : repo.getConfig().get(WorkingTreeOptions.KEY);
if (entry.getFileMode() == FileMode.SYMLINK
&& opt.getSymLinks() == SymLinks.TRUE) {
byte[] bytes = ol.getBytes();
@@ -1561,6 +1606,60 @@ public class DirCacheCheckout {
CheckoutMetadata checkoutMetadata, ObjectLoader ol,
WorkingTreeOptions opt, OutputStream os)
throws IOException {
+ getContent(repo, path, checkoutMetadata, ol::openStream, opt, os);
+ }
+
+
+ /**
+ * Something that can supply an {@link InputStream}.
+ *
+ * @since 6.3
+ */
+ public interface StreamSupplier {
+
+ /**
+ * Loads the input stream.
+ *
+ * @return the loaded stream
+ * @throws IOException
+ * if any reading error occurs
+ */
+ InputStream load() throws IOException;
+ }
+
+ /**
+ * Return filtered content for blob contents. EOL handling and smudge-filter
+ * handling are applied in the same way as it would be done during a
+ * checkout.
+ *
+ * @param repo
+ * the repository
+ * @param path
+ * the path used to determine the correct filters for the object
+ * @param checkoutMetadata
+ * containing
+ * <ul>
+ * <li>smudgeFilterCommand to be run for smudging the object</li>
+ * <li>eolStreamType used for stream conversion (can be
+ * null)</li>
+ * </ul>
+ * @param inputStream
+ * A supplier for the raw content of the object. Each call should
+ * yield a fresh stream of the same object.
+ * @param opt
+ * the working tree options where only 'core.autocrlf' is used
+ * for EOL handling if 'checkoutMetadata.eolStreamType' is not
+ * valid
+ * @param os
+ * the output stream the filtered content is written to. The
+ * caller is responsible to close the stream.
+ * @throws IOException
+ * @since 6.3
+ */
+ public static void getContent(Repository repo, String path,
+ CheckoutMetadata checkoutMetadata, StreamSupplier inputStream,
+ WorkingTreeOptions opt, OutputStream os)
+ throws IOException {
EolStreamType nonNullEolStreamType;
if (checkoutMetadata.eolStreamType != null) {
nonNullEolStreamType = checkoutMetadata.eolStreamType;
@@ -1574,21 +1673,23 @@ public class DirCacheCheckout {
if (checkoutMetadata.smudgeFilterCommand != null) {
if (FilterCommandRegistry
.isRegistered(checkoutMetadata.smudgeFilterCommand)) {
- runBuiltinFilterCommand(repo, checkoutMetadata, ol,
+ runBuiltinFilterCommand(repo, checkoutMetadata, inputStream,
channel);
} else {
- runExternalFilterCommand(repo, path, checkoutMetadata, ol,
+ runExternalFilterCommand(repo, path, checkoutMetadata, inputStream,
channel);
}
} else {
- ol.copyTo(channel);
+ try (InputStream in = inputStream.load()) {
+ in.transferTo(channel);
+ }
}
}
}
// Run an external filter command
private static void runExternalFilterCommand(Repository repo, String path,
- CheckoutMetadata checkoutMetadata, ObjectLoader ol,
+ CheckoutMetadata checkoutMetadata, StreamSupplier inputStream,
OutputStream channel) throws IOException {
FS fs = repo.getFS();
ProcessBuilder filterProcessBuilder = fs.runInShell(
@@ -1600,7 +1701,9 @@ public class DirCacheCheckout {
int rc;
try {
// TODO: wire correctly with AUTOCRLF
- result = fs.execute(filterProcessBuilder, ol.openStream());
+ try (InputStream in = inputStream.load()) {
+ result = fs.execute(filterProcessBuilder, in);
+ }
rc = result.getRc();
if (rc == 0) {
result.getStdout().writeTo(channel,
@@ -1621,31 +1724,35 @@ public class DirCacheCheckout {
// Run a builtin filter command
private static void runBuiltinFilterCommand(Repository repo,
- CheckoutMetadata checkoutMetadata, ObjectLoader ol,
+ CheckoutMetadata checkoutMetadata, StreamSupplier inputStream,
OutputStream channel) throws MissingObjectException, IOException {
boolean isMandatory = repo.getConfig().getBoolean(
ConfigConstants.CONFIG_FILTER_SECTION,
ConfigConstants.CONFIG_SECTION_LFS,
ConfigConstants.CONFIG_KEY_REQUIRED, false);
FilterCommand command = null;
- try {
- command = FilterCommandRegistry.createFilterCommand(
- checkoutMetadata.smudgeFilterCommand, repo, ol.openStream(),
- channel);
- } catch (IOException e) {
- LOG.error(JGitText.get().failedToDetermineFilterDefinition, e);
- if (!isMandatory) {
- // In case an IOException occurred during creating of the
- // command then proceed as if there would not have been a
- // builtin filter (only if the filter is not mandatory).
- ol.copyTo(channel);
- } else {
- throw e;
+ try (InputStream in = inputStream.load()) {
+ try {
+ command = FilterCommandRegistry.createFilterCommand(
+ checkoutMetadata.smudgeFilterCommand, repo, in,
+ channel);
+ } catch (IOException e) {
+ LOG.error(JGitText.get().failedToDetermineFilterDefinition, e);
+ if (!isMandatory) {
+ // In case an IOException occurred during creating of the
+ // command then proceed as if there would not have been a
+ // builtin filter (only if the filter is not mandatory).
+ try (InputStream again = inputStream.load()) {
+ again.transferTo(channel);
+ }
+ } else {
+ throw e;
+ }
}
- }
- if (command != null) {
- while (command.run() != -1) {
- // loop as long as command.run() tells there is work to do
+ if (command != null) {
+ while (command.run() != -1) {
+ // loop as long as command.run() tells there is work to do
+ }
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
index e6626aece3..f63cc6d644 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
@@ -23,6 +23,7 @@ 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.dircache.InvalidPathException;
import org.eclipse.jgit.gitrepo.RepoCommand.ManifestErrorException;
import org.eclipse.jgit.gitrepo.RepoCommand.RemoteFile;
import org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader;
@@ -138,7 +139,7 @@ class BareSuperprojectWriter {
}
// In the last try, just propagate the exceptions
return commitTreeOnCurrentTip(inserter, rw, treeId);
- } catch (IOException | InterruptedException e) {
+ } catch (IOException | InterruptedException | InvalidPathException e) {
throw new ManifestErrorException(e);
}
}
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 efdb8e42e3..964debcccb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -265,6 +265,8 @@ public class JGitText extends TranslationBundle {
/***/ public String deleteRequiresZeroNewId;
/***/ public String deleteTagUnexpectedResult;
/***/ public String deletingNotSupported;
+ /***/ public String depthMustBeAt1;
+ /***/ public String depthWithUnshallow;
/***/ public String destinationIsNotAWildcard;
/***/ public String detachedHeadDetected;
/***/ public String diffToolNotGivenError;
@@ -546,6 +548,7 @@ public class JGitText extends TranslationBundle {
/***/ public String nothingToFetch;
/***/ public String nothingToPush;
/***/ public String notMergedExceptionMessage;
+ /***/ public String notShallowedUnshallow;
/***/ public String noXMLParserAvailable;
/***/ public String objectAtHasBadZlibStream;
/***/ public String objectIsCorrupt;
@@ -615,6 +618,7 @@ public class JGitText extends TranslationBundle {
/***/ public String pushOptionsNotSupported;
/***/ public String rawLogMessageDoesNotParseAsLogEntry;
/***/ public String readConfigFailed;
+ /***/ public String readShallowFailed;
/***/ public String readFileStoreAttributesFailed;
/***/ public String readerIsRequired;
/***/ public String readingObjectsFromLocalRepositoryFailed;
@@ -690,6 +694,7 @@ public class JGitText extends TranslationBundle {
/***/ public String serviceNotPermitted;
/***/ public String sha1CollisionDetected;
/***/ public String shallowCommitsAlreadyInitialized;
+ /***/ public String shallowNotSupported;
/***/ public String shallowPacksRequireDepthWalk;
/***/ public String shortCompressedStreamAt;
/***/ public String shortReadOfBlock;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java
new file mode 100644
index 0000000000..d65624fc6a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022, Simeon Andreev 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.internal.diff;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.diff.RenameDetector;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+
+/**
+ * Provides rename detection in special cases such as blame, where only a subset
+ * of the renames detected by {@link RenameDetector} is of interest.
+ */
+public class FilteredRenameDetector {
+
+ private final RenameDetector renameDetector;
+
+ /**
+ * @param repository
+ * The repository in which to check for renames.
+ */
+ public FilteredRenameDetector(Repository repository) {
+ this(new RenameDetector(repository));
+ }
+
+ /**
+ * @param renameDetector
+ * The {@link RenameDetector} to use when checking for renames.
+ */
+ public FilteredRenameDetector(RenameDetector renameDetector) {
+ this.renameDetector = renameDetector;
+ }
+
+ /**
+ * @param diffs
+ * The set of changes to check.
+ * @param pathFilter
+ * Filter out changes that didn't affect this path.
+ * @return The subset of changes that affect only the filtered path.
+ * @throws IOException
+ */
+ public List<DiffEntry> compute(List<DiffEntry> diffs,
+ PathFilter pathFilter) throws IOException {
+ return compute(diffs, Arrays.asList(pathFilter));
+ }
+
+ /**
+ * Tries to avoid computation overhead in {@link RenameDetector#compute()}
+ * by filtering diffs related to the path filters only.
+ * <p>
+ * Note: current implementation only optimizes added or removed diffs,
+ * further optimization is possible.
+ *
+ * @param changes
+ * The set of changes to check.
+ * @param pathFilters
+ * Filter out changes that didn't affect these paths.
+ * @return The subset of changes that affect only the filtered paths.
+ * @throws IOException
+ * @see RenameDetector#compute()
+ */
+ public List<DiffEntry> compute(List<DiffEntry> changes,
+ List<PathFilter> pathFilters) throws IOException {
+
+ if (pathFilters == null) {
+ throw new IllegalArgumentException("Must specify path filters"); //$NON-NLS-1$
+ }
+
+ Set<String> paths = new HashSet<>(pathFilters.size());
+ for (PathFilter pathFilter : pathFilters) {
+ paths.add(pathFilter.getPath());
+ }
+
+ List<DiffEntry> filtered = new ArrayList<>();
+
+ // For new path: skip ADD's that don't match given paths
+ for (DiffEntry diff : changes) {
+ ChangeType changeType = diff.getChangeType();
+ if (changeType != ChangeType.ADD
+ || paths.contains(diff.getNewPath())) {
+ filtered.add(diff);
+ }
+ }
+
+ renameDetector.reset();
+ renameDetector.addAll(filtered);
+ List<DiffEntry> sourceChanges = renameDetector.compute();
+
+ filtered.clear();
+
+ // For old path: skip DELETE's that don't match given paths
+ for (DiffEntry diff : changes) {
+ ChangeType changeType = diff.getChangeType();
+ if (changeType != ChangeType.DELETE
+ || paths.contains(diff.getOldPath())) {
+ filtered.add(diff);
+ }
+ }
+
+ renameDetector.reset();
+ renameDetector.addAll(filtered);
+ List<DiffEntry> targetChanges = renameDetector.compute();
+
+ List<DiffEntry> result = new ArrayList<>();
+
+ for (DiffEntry sourceChange : sourceChanges) {
+ if (paths.contains(sourceChange.getNewPath())) {
+ result.add(sourceChange);
+ }
+ }
+ for (DiffEntry targetChange : targetChanges) {
+ if (paths.contains(targetChange.getOldPath())) {
+ result.add(targetChange);
+ }
+ }
+
+ renameDetector.reset();
+ return result;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
index 668adeab65..ebef5247e6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
@@ -27,6 +27,7 @@ import org.eclipse.jgit.util.FS_POSIX;
import org.eclipse.jgit.util.FS_Win32;
import org.eclipse.jgit.util.FS_Win32_Cygwin;
import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
/**
* Runs a command with help of FS.
@@ -87,7 +88,9 @@ public class CommandExecutor {
+ "execError: " + execError + "\n" //$NON-NLS-1$ //$NON-NLS-2$
+ "stderr: \n" //$NON-NLS-1$
+ new String(
- result.getStderr().toByteArray()),
+ result.getStderr().toByteArray(),
+ SystemReader.getInstance()
+ .getDefaultCharset()),
result, execError);
}
}
@@ -202,7 +205,8 @@ public class CommandExecutor {
commandFile = File.createTempFile(".__", //$NON-NLS-1$
"__jgit_tool" + fileExtension); //$NON-NLS-1$
try (OutputStream outStream = new FileOutputStream(commandFile)) {
- byte[] strToBytes = command.getBytes();
+ byte[] strToBytes = command
+ .getBytes(SystemReader.getInstance().getDefaultCharset());
outStream.write(strToBytes);
outStream.close();
}
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
index 7cedd82995..d0034df3bc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -103,7 +103,7 @@ public class DiffTools {
* @param noToolHandler
* The handler to use when needing to inform the user, that no
* tool is configured.
- * @return the optioanl result of executing the tool if it was executed
+ * @return the optional result of executing the tool if it was executed
* @throws ToolException
* when the tool fails
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
index 7cc5bb50d9..73d3588906 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
@@ -11,7 +11,7 @@
package org.eclipse.jgit.internal.diffmergetool;
import org.eclipse.jgit.util.FS.ExecutionResult;
-
+import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -114,7 +114,8 @@ public class ToolException extends Exception {
return ""; //$NON-NLS-1$
}
try {
- return new String(result.getStderr().toByteArray());
+ return new String(result.getStderr().toByteArray(),
+ SystemReader.getInstance().getDefaultCharset());
} catch (Exception e) {
LOG.warn("Failed to retrieve standard error output", e); //$NON-NLS-1$
}
@@ -129,7 +130,8 @@ public class ToolException extends Exception {
return ""; //$NON-NLS-1$
}
try {
- return new String(result.getStdout().toByteArray());
+ return new String(result.getStdout().toByteArray(),
+ SystemReader.getInstance().getDefaultCharset());
} catch (Exception e) {
LOG.warn("Failed to retrieve standard output", e); //$NON-NLS-1$
}
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 99da222395..5a8207ed01 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
@@ -1,3 +1,12 @@
+/*
+ * Copyright (C) 2011, 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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
package org.eclipse.jgit.internal.storage.dfs;
import java.io.ByteArrayOutputStream;
@@ -6,13 +15,16 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefDatabase;
/**
@@ -98,6 +110,7 @@ public class InMemoryRepository extends DfsRepository {
public static class MemObjDatabase extends DfsObjDatabase {
private List<DfsPackDescription> packs = new ArrayList<>();
private int blockSize;
+ private Set<ObjectId> shallowCommits = Collections.emptySet();
MemObjDatabase(DfsRepository repo) {
super(repo, new DfsReaderOptions());
@@ -167,6 +180,16 @@ public class InMemoryRepository extends DfsRepository {
}
@Override
+ public Set<ObjectId> getShallowCommits() throws IOException {
+ return shallowCommits;
+ }
+
+ @Override
+ public void setShallowCommits(Set<ObjectId> shallowCommits) {
+ this.shallowCommits = shallowCommits;
+ }
+
+ @Override
public long getApproximateObjectCount() {
long count = 0;
for (DfsPackDescription p : packs) {
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 094fdc1559..9272bf3f59 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
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
- * Copyright (C) 2010, JetBrains s.r.o. and others
+ * Copyright (C) 2010, 2022 JetBrains s.r.o. 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
@@ -117,10 +117,15 @@ class CachedObjectDirectory extends FileObjectDatabase {
}
@Override
- Set<ObjectId> getShallowCommits() throws IOException {
+ public Set<ObjectId> getShallowCommits() throws IOException {
return wrapped.getShallowCommits();
}
+ @Override
+ public void setShallowCommits(Set<ObjectId> shallowCommits) throws IOException {
+ wrapped.setShallowCommits(shallowCommits);
+ }
+
private CachedObjectDirectory[] myAlternates() {
if (alts == null) {
ObjectDirectory.AlternateHandle[] src = wrapped.myAlternates();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java
index 01dd27d9fb..e97ed393a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, Google Inc. and others
+ * Copyright (C) 2010, 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
@@ -50,8 +50,6 @@ abstract class FileObjectDatabase extends ObjectDatabase {
abstract FS getFS();
- abstract Set<ObjectId> getShallowCommits() throws IOException;
-
abstract void selectObjectRepresentation(PackWriter packer,
ObjectToPack otp, WindowCursor curs) throws IOException;
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 06c8cad3ac..7699439128 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009, 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
@@ -19,6 +19,7 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -560,31 +561,82 @@ public class ObjectDirectory extends FileObjectDatabase {
}
@Override
- Set<ObjectId> getShallowCommits() throws IOException {
+ public Set<ObjectId> getShallowCommits() throws IOException {
if (shallowFile == null || !shallowFile.isFile())
return Collections.emptySet();
if (shallowFileSnapshot == null
|| shallowFileSnapshot.isModified(shallowFile)) {
- shallowCommitsIds = new HashSet<>();
+ try {
+ shallowCommitsIds = FileUtils.readWithRetries(shallowFile,
+ f -> {
+ FileSnapshot newSnapshot = FileSnapshot.save(f);
+ HashSet<ObjectId> result = new HashSet<>();
+ try (BufferedReader reader = open(f)) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!ObjectId.isId(line)) {
+ throw new IOException(
+ MessageFormat.format(JGitText
+ .get().badShallowLine,
+ f.getAbsolutePath(),
+ line));
+
+ }
+ result.add(ObjectId.fromString(line));
+ }
+ }
+ shallowFileSnapshot = newSnapshot;
+ return result;
+ });
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().readShallowFailed,
+ shallowFile.getAbsolutePath()),
+ e);
+ }
+ }
- try (BufferedReader reader = open(shallowFile)) {
- String line;
- while ((line = reader.readLine()) != null) {
- try {
- shallowCommitsIds.add(ObjectId.fromString(line));
- } catch (IllegalArgumentException ex) {
- throw new IOException(MessageFormat
- .format(JGitText.get().badShallowLine, line),
- ex);
+ return shallowCommitsIds;
+ }
+
+ @Override
+ public void setShallowCommits(Set<ObjectId> shallowCommits) throws IOException {
+ this.shallowCommitsIds = shallowCommits;
+ LockFile lock = new LockFile(shallowFile);
+ if (!lock.lock()) {
+ throw new IOException(MessageFormat.format(JGitText.get().lockError,
+ shallowFile.getAbsolutePath()));
+ }
+
+ try {
+ if (shallowCommits.isEmpty()) {
+ if (shallowFile.isFile()) {
+ shallowFile.delete();
+ }
+ } else {
+ try (OutputStream out = lock.getOutputStream()) {
+ for (ObjectId shallowCommit : shallowCommits) {
+ byte[] buf = new byte[Constants.OBJECT_ID_STRING_LENGTH + 1];
+ shallowCommit.copyTo(buf, 0);
+ buf[Constants.OBJECT_ID_STRING_LENGTH] = '\n';
+ out.write(buf);
}
+ } finally {
+ lock.commit();
}
}
+ } finally {
+ lock.unlock();
+ }
+ if (shallowCommits.isEmpty()) {
+ shallowFileSnapshot = FileSnapshot.DIRTY;
+ } else {
shallowFileSnapshot = FileSnapshot.save(shallowFile);
}
-
- return shallowCommitsIds;
}
void closeAllPackHandles(File packFile) {
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 cf2e69dbb5..30a0074195 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2008, Google Inc.
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2017, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2006, 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
@@ -747,6 +747,13 @@ public final class Constants {
*/
public static final String LOCK_SUFFIX = ".lock"; //$NON-NLS-1$
+ /**
+ * Depth used to unshallow a repository
+ *
+ * @since 6.3
+ */
+ public static final int INFINITE_DEPTH = 0x7fffffff;
+
private Constants() {
// Hide the default constructor
}
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 70009cba35..a39766cbd0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009, 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
@@ -11,6 +11,8 @@
package org.eclipse.jgit.lib;
import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -22,6 +24,9 @@ import org.eclipse.jgit.errors.MissingObjectException;
* {@link org.eclipse.jgit.lib.ObjectId}.
*/
public abstract class ObjectDatabase implements AutoCloseable {
+
+ private static final Set<ObjectId> shallowCommits = Collections.emptySet();
+
/**
* Initialize a new database instance for access.
*/
@@ -72,6 +77,35 @@ public abstract class ObjectDatabase implements AutoCloseable {
public abstract ObjectReader newReader();
/**
+ * @return the shallow commits of the current repository
+ *
+ * @throws IOException the database could not be read
+ *
+ * @since 6.3
+ */
+ public Set<ObjectId> getShallowCommits() throws IOException {
+ return shallowCommits;
+ }
+
+
+ /**
+ * Update the shallow commits of the current repository
+ *
+ * @param shallowCommits the new shallow commits
+ *
+ * @throws IOException the database could not be updated
+ *
+ * @since 6.3
+ */
+ public void setShallowCommits(Set<ObjectId> shallowCommits)
+ throws IOException {
+ if (!shallowCommits.isEmpty()) {
+ throw new UnsupportedOperationException(
+ "Shallow commits expected to be empty."); //$NON-NLS-1$
+ }
+ }
+
+ /**
* Close any resources held by this database.
*/
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index bf2a78f6b3..df6068925b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -195,9 +195,6 @@ public class RecursiveMerger extends ResolveMerger {
inCore = oldIncore;
dircache = oldDircache;
workingTreeIterator = oldWTreeIt;
- toBeCheckedOut.clear();
- toBeDeleted.clear();
- modifiedFiles.clear();
unmergedPaths.clear();
mergeResults.clear();
failingPaths.clear();
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 b9ab1d1b7a..e56513d4e9 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,8 @@
* 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, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2022 Thomas Wolf <twolf@apache.org>
+ * Copyright (C) 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
@@ -20,9 +21,8 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import java.io.BufferedOutputStream;
+import java.io.Closeable;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -32,12 +32,15 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
@@ -49,13 +52,12 @@ import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
+import org.eclipse.jgit.dircache.DirCacheCheckout.StreamSupplier;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.BinaryBlobException;
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.IndexWriteException;
-import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
@@ -64,6 +66,7 @@ import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.storage.pack.PackConfig;
@@ -86,6 +89,595 @@ import org.eclipse.jgit.util.io.EolStreamTypeUtil;
* A three-way merger performing a content-merge if necessary
*/
public class ResolveMerger extends ThreeWayMerger {
+
+ /**
+ * Handles work tree updates on both the checkout and the index.
+ * <p>
+ * You should use a single instance for all of your file changes. In case of
+ * an error, make sure your instance is released, and initiate a new one if
+ * necessary.
+ *
+ * @since 6.3.1
+ */
+ protected static class WorkTreeUpdater implements Closeable {
+
+ /**
+ * The result of writing the index changes.
+ */
+ public static class Result {
+
+ private final List<String> modifiedFiles = new LinkedList<>();
+
+ private final List<String> failedToDelete = new LinkedList<>();
+
+ private ObjectId treeId = null;
+
+ /**
+ * @return Modified tree ID if any, or null otherwise.
+ */
+ public ObjectId getTreeId() {
+ return treeId;
+ }
+
+ /**
+ * @return Files that couldn't be deleted.
+ */
+ public List<String> getFailedToDelete() {
+ return failedToDelete;
+ }
+
+ /**
+ * @return Files modified during this operation.
+ */
+ public List<String> getModifiedFiles() {
+ return modifiedFiles;
+ }
+ }
+
+ Result result = new Result();
+
+ /**
+ * The repository this handler operates on.
+ */
+ @Nullable
+ private final Repository repo;
+
+ /**
+ * Set to true if this operation should work in-memory. The repo's
+ * dircache and workingtree are not touched by this method. Eventually
+ * needed files are created as temporary files and a new empty,
+ * in-memory dircache will be used instead the repo's one. Often used
+ * for bare repos where the repo doesn't even have a workingtree and
+ * dircache.
+ */
+ private final boolean inCore;
+
+ private final ObjectInserter inserter;
+
+ private final ObjectReader reader;
+
+ private DirCache dirCache;
+
+ private boolean implicitDirCache = false;
+
+ /**
+ * Builder to update the dir cache during this operation.
+ */
+ private DirCacheBuilder builder;
+
+ /**
+ * The {@link WorkingTreeOptions} are needed to determine line endings
+ * for affected files.
+ */
+ private WorkingTreeOptions workingTreeOptions;
+
+ /**
+ * The size limit (bytes) which controls a file to be stored in
+ * {@code Heap} or {@code LocalFile} during the operation.
+ */
+ private int inCoreFileSizeLimit;
+
+ /**
+ * If the operation has nothing to do for a file but check it out at the
+ * end of the operation, it can be added here.
+ */
+ private final Map<String, DirCacheEntry> toBeCheckedOut = new HashMap<>();
+
+ /**
+ * Files in this list will be deleted from the local copy at the end of
+ * the operation.
+ */
+ private final TreeMap<String, File> toBeDeleted = new TreeMap<>();
+
+ /**
+ * Keeps {@link CheckoutMetadata} for {@link #checkout()}.
+ */
+ private Map<String, CheckoutMetadata> checkoutMetadataByPath;
+
+ /**
+ * Keeps {@link CheckoutMetadata} for {@link #revertModifiedFiles()}.
+ */
+ private Map<String, CheckoutMetadata> cleanupMetadataByPath;
+
+ /**
+ * Whether the changes were successfully written.
+ */
+ private boolean indexChangesWritten;
+
+ /**
+ * @param repo
+ * the {@link Repository}.
+ * @param dirCache
+ * if set, use the provided dir cache. Otherwise, use the
+ * default repository one
+ */
+ private WorkTreeUpdater(Repository repo, DirCache dirCache) {
+ this.repo = repo;
+ this.dirCache = dirCache;
+
+ this.inCore = false;
+ this.inserter = repo.newObjectInserter();
+ this.reader = inserter.newReader();
+ Config config = repo.getConfig();
+ this.workingTreeOptions = config.get(WorkingTreeOptions.KEY);
+ this.inCoreFileSizeLimit = getInCoreFileSizeLimit(config);
+ this.checkoutMetadataByPath = new HashMap<>();
+ this.cleanupMetadataByPath = new HashMap<>();
+ }
+
+ /**
+ * Creates a new {@link WorkTreeUpdater} for the given repository.
+ *
+ * @param repo
+ * the {@link Repository}.
+ * @param dirCache
+ * if set, use the provided dir cache. Otherwise, use the
+ * default repository one
+ * @return the {@link WorkTreeUpdater}.
+ */
+ public static WorkTreeUpdater createWorkTreeUpdater(Repository repo,
+ DirCache dirCache) {
+ return new WorkTreeUpdater(repo, dirCache);
+ }
+
+ /**
+ * @param repo
+ * the {@link Repository}.
+ * @param dirCache
+ * if set, use the provided dir cache. Otherwise, creates a
+ * new one
+ * @param oi
+ * to use for writing the modified objects with.
+ */
+ private WorkTreeUpdater(Repository repo, DirCache dirCache,
+ ObjectInserter oi) {
+ this.repo = repo;
+ this.dirCache = dirCache;
+ this.inserter = oi;
+
+ this.inCore = true;
+ this.reader = oi.newReader();
+ if (repo != null) {
+ this.inCoreFileSizeLimit = getInCoreFileSizeLimit(
+ repo.getConfig());
+ }
+ }
+
+ /**
+ * Creates a new {@link WorkTreeUpdater} that works in memory only.
+ *
+ * @param repo
+ * the {@link Repository}.
+ * @param dirCache
+ * if set, use the provided dir cache. Otherwise, creates a
+ * new one
+ * @param oi
+ * to use for writing the modified objects with.
+ * @return the {@link WorkTreeUpdater}
+ */
+ public static WorkTreeUpdater createInCoreWorkTreeUpdater(
+ Repository repo, DirCache dirCache, ObjectInserter oi) {
+ return new WorkTreeUpdater(repo, dirCache, oi);
+ }
+
+ private static int getInCoreFileSizeLimit(Config config) {
+ return config.getInt(ConfigConstants.CONFIG_MERGE_SECTION,
+ ConfigConstants.CONFIG_KEY_IN_CORE_LIMIT, 10 << 20);
+ }
+
+ /**
+ * Gets the size limit for in-core files in this config.
+ *
+ * @return the size
+ */
+ public int getInCoreFileSizeLimit() {
+ return inCoreFileSizeLimit;
+ }
+
+ /**
+ * Gets dir cache for the repo. Locked if not inCore.
+ *
+ * @return the result dir cache
+ * @throws IOException
+ * is case the dir cache cannot be read
+ */
+ public DirCache getLockedDirCache() throws IOException {
+ if (dirCache == null) {
+ implicitDirCache = true;
+ if (inCore) {
+ dirCache = DirCache.newInCore();
+ } else {
+ dirCache = nonNullRepo().lockDirCache();
+ }
+ }
+ if (builder == null) {
+ builder = dirCache.builder();
+ }
+ return dirCache;
+ }
+
+ /**
+ * Creates a {@link DirCacheBuildIterator} for the builder of this
+ * {@link WorkTreeUpdater}.
+ *
+ * @return the {@link DirCacheBuildIterator}
+ */
+ public DirCacheBuildIterator createDirCacheBuildIterator() {
+ return new DirCacheBuildIterator(builder);
+ }
+
+ /**
+ * Writes the changes to the working tree (but not to the index).
+ *
+ * @param shouldCheckoutTheirs
+ * before committing the changes
+ * @throws IOException
+ * if any of the writes fail
+ */
+ public void writeWorkTreeChanges(boolean shouldCheckoutTheirs)
+ throws IOException {
+ handleDeletedFiles();
+
+ if (inCore) {
+ builder.finish();
+ return;
+ }
+ if (shouldCheckoutTheirs) {
+ // No problem found. The only thing left to be done is to
+ // check out all files from "theirs" which have been selected to
+ // go into the new index.
+ checkout();
+ }
+
+ // All content operations are successfully done. If we can now write
+ // the
+ // new index we are on quite safe ground. Even if the checkout of
+ // files coming from "theirs" fails the user can work around such
+ // failures by checking out the index again.
+ if (!builder.commit()) {
+ revertModifiedFiles();
+ throw new IndexWriteException();
+ }
+ }
+
+ /**
+ * Writes the changes to the index.
+ *
+ * @return the {@link Result} of the operation.
+ * @throws IOException
+ * if any of the writes fail
+ */
+ public Result writeIndexChanges() throws IOException {
+ result.treeId = getLockedDirCache().writeTree(inserter);
+ indexChangesWritten = true;
+ return result;
+ }
+
+ /**
+ * Adds a {@link DirCacheEntry} for direct checkout and remembers its
+ * {@link CheckoutMetadata}.
+ *
+ * @param path
+ * of the entry
+ * @param entry
+ * to add
+ * @param cleanupStreamType
+ * to use for the cleanup metadata
+ * @param cleanupSmudgeCommand
+ * to use for the cleanup metadata
+ * @param checkoutStreamType
+ * to use for the checkout metadata
+ * @param checkoutSmudgeCommand
+ * to use for the checkout metadata
+ */
+ public void addToCheckout(String path, DirCacheEntry entry,
+ EolStreamType cleanupStreamType, String cleanupSmudgeCommand,
+ EolStreamType checkoutStreamType,
+ String checkoutSmudgeCommand) {
+ if (entry != null) {
+ // In some cases, we just want to add the metadata.
+ toBeCheckedOut.put(path, entry);
+ }
+ addCheckoutMetadata(cleanupMetadataByPath, path, cleanupStreamType,
+ cleanupSmudgeCommand);
+ addCheckoutMetadata(checkoutMetadataByPath, path,
+ checkoutStreamType, checkoutSmudgeCommand);
+ }
+
+ /**
+ * Gets a map which maps the paths of files which have to be checked out
+ * because the operation created new fully-merged content for this file
+ * into the index.
+ * <p>
+ * This means: the operation wrote a new stage 0 entry for this path.
+ * </p>
+ *
+ * @return the map
+ */
+ public Map<String, DirCacheEntry> getToBeCheckedOut() {
+ return toBeCheckedOut;
+ }
+
+ /**
+ * Remembers the given file to be deleted.
+ * <p>
+ * Note the actual deletion is only done in
+ * {@link #writeWorkTreeChanges}.
+ *
+ * @param path
+ * of the file to be deleted
+ * @param file
+ * to be deleted
+ * @param streamType
+ * to use for cleanup metadata
+ * @param smudgeCommand
+ * to use for cleanup metadata
+ */
+ public void deleteFile(String path, File file, EolStreamType streamType,
+ String smudgeCommand) {
+ toBeDeleted.put(path, file);
+ if (file != null && file.isFile()) {
+ addCheckoutMetadata(cleanupMetadataByPath, path, streamType,
+ smudgeCommand);
+ }
+ }
+
+ /**
+ * Remembers the {@link CheckoutMetadata} for the given path; it may be
+ * needed in {@link #checkout()} or in {@link #revertModifiedFiles()}.
+ *
+ * @param map
+ * to add the metadata to
+ * @param path
+ * of the current node
+ * @param streamType
+ * to use for the metadata
+ * @param smudgeCommand
+ * to use for the metadata
+ */
+ private void addCheckoutMetadata(Map<String, CheckoutMetadata> map,
+ String path, EolStreamType streamType, String smudgeCommand) {
+ if (inCore || map == null) {
+ return;
+ }
+ map.put(path, new CheckoutMetadata(streamType, smudgeCommand));
+ }
+
+ /**
+ * Detects if CRLF conversion has been configured.
+ * <p>
+ * </p>
+ * See {@link EolStreamTypeUtil#detectStreamType} for more info.
+ *
+ * @param attributes
+ * of the file for which the type is to be detected
+ * @return the detected type
+ */
+ public EolStreamType detectCheckoutStreamType(Attributes attributes) {
+ if (inCore) {
+ return null;
+ }
+ return EolStreamTypeUtil.detectStreamType(OperationType.CHECKOUT_OP,
+ workingTreeOptions, attributes);
+ }
+
+ private void handleDeletedFiles() {
+ // Iterate in reverse so that "folder/file" is deleted before
+ // "folder". Otherwise, this could result in a failing path because
+ // of a non-empty directory, for which delete() would fail.
+ for (String path : toBeDeleted.descendingKeySet()) {
+ File file = inCore ? null : toBeDeleted.get(path);
+ if (file != null && !file.delete()) {
+ if (!file.isDirectory()) {
+ result.failedToDelete.add(path);
+ }
+ }
+ }
+ }
+
+ /**
+ * Marks the given path as modified in the operation.
+ *
+ * @param path
+ * to mark as modified
+ */
+ public void markAsModified(String path) {
+ result.modifiedFiles.add(path);
+ }
+
+ /**
+ * Gets the list of files which were modified in this operation.
+ *
+ * @return the list
+ */
+ public List<String> getModifiedFiles() {
+ return result.modifiedFiles;
+ }
+
+ private void checkout() throws NoWorkTreeException, IOException {
+ for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
+ .entrySet()) {
+ DirCacheEntry dirCacheEntry = entry.getValue();
+ if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
+ new File(nonNullRepo().getWorkTree(), entry.getKey())
+ .mkdirs();
+ } else {
+ DirCacheCheckout.checkoutEntry(repo, dirCacheEntry, reader,
+ false, checkoutMetadataByPath.get(entry.getKey()),
+ workingTreeOptions);
+ result.modifiedFiles.add(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Reverts any uncommitted changes in the worktree. We know that for all
+ * modified files the old content was in the old index and the index
+ * contained only stage 0. In case of inCore operation just clear the
+ * history of modified files.
+ *
+ * @throws IOException
+ * in case the cleaning up failed
+ */
+ public void revertModifiedFiles() throws IOException {
+ if (inCore) {
+ result.modifiedFiles.clear();
+ return;
+ }
+ if (indexChangesWritten) {
+ return;
+ }
+ for (String path : result.modifiedFiles) {
+ DirCacheEntry entry = dirCache.getEntry(path);
+ if (entry != null) {
+ DirCacheCheckout.checkoutEntry(repo, entry, reader, false,
+ cleanupMetadataByPath.get(path),
+ workingTreeOptions);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (implicitDirCache) {
+ dirCache.unlock();
+ }
+ }
+
+ /**
+ * Updates the file in the checkout with the given content.
+ *
+ * @param inputStream
+ * the content to be updated
+ * @param streamType
+ * for parsing the content
+ * @param smudgeCommand
+ * for formatting the content
+ * @param path
+ * of the file to be updated
+ * @param file
+ * to be updated
+ * @throws IOException
+ * if the file cannot be updated
+ */
+ public void updateFileWithContent(StreamSupplier inputStream,
+ EolStreamType streamType, String smudgeCommand, String path,
+ File file) throws IOException {
+ if (inCore) {
+ return;
+ }
+ CheckoutMetadata metadata = new CheckoutMetadata(streamType,
+ smudgeCommand);
+
+ try (OutputStream outputStream = new FileOutputStream(file)) {
+ DirCacheCheckout.getContent(repo, path, metadata, inputStream,
+ workingTreeOptions, outputStream);
+ }
+ }
+
+ /**
+ * Creates a path with the given content, and adds it to the specified
+ * stage to the index builder.
+ *
+ * @param input
+ * the content to be updated
+ * @param path
+ * of the file to be updated
+ * @param fileMode
+ * of the modified file
+ * @param entryStage
+ * of the new entry
+ * @param lastModified
+ * instant of the modified file
+ * @param len
+ * of the content
+ * @param lfsAttribute
+ * for checking for LFS enablement
+ * @return the entry which was added to the index
+ * @throws IOException
+ * if inserting the content fails
+ */
+ public DirCacheEntry insertToIndex(InputStream input, byte[] path,
+ FileMode fileMode, int entryStage, Instant lastModified,
+ int len, Attribute lfsAttribute) throws IOException {
+ return addExistingToIndex(insertResult(input, lfsAttribute, len),
+ path, fileMode, entryStage, lastModified, len);
+ }
+
+ /**
+ * Adds a path with the specified stage to the index builder.
+ *
+ * @param objectId
+ * of the existing object to add
+ * @param path
+ * of the modified file
+ * @param fileMode
+ * of the modified file
+ * @param entryStage
+ * of the new entry
+ * @param lastModified
+ * instant of the modified file
+ * @param len
+ * of the modified file content
+ * @return the entry which was added to the index
+ */
+ public DirCacheEntry addExistingToIndex(ObjectId objectId, byte[] path,
+ FileMode fileMode, int entryStage, Instant lastModified,
+ int len) {
+ DirCacheEntry dce = new DirCacheEntry(path, entryStage);
+ dce.setFileMode(fileMode);
+ if (lastModified != null) {
+ dce.setLastModified(lastModified);
+ }
+ dce.setLength(inCore ? 0 : len);
+ dce.setObjectId(objectId);
+ builder.add(dce);
+ return dce;
+ }
+
+ private ObjectId insertResult(InputStream input, Attribute lfsAttribute,
+ long length) throws IOException {
+ try (LfsInputStream is = LfsFactory.getInstance()
+ .applyCleanFilter(repo, input, length, lfsAttribute)) {
+ return inserter.insert(OBJ_BLOB, is.getLength(), is);
+ }
+ }
+
+ /**
+ * Gets the non-null repository instance of this
+ * {@link WorkTreeUpdater}.
+ *
+ * @return non-null repository instance
+ * @throws NullPointerException
+ * if the handler was constructed without a repository.
+ */
+ @NonNull
+ private Repository nonNullRepo() throws NullPointerException {
+ return Objects.requireNonNull(repo,
+ () -> JGitText.get().repositoryIsRequired);
+ }
+ }
+
/**
* If the merge fails (means: not stopped because of unresolved conflicts)
* this enum is used to explain why it failed
@@ -149,11 +741,11 @@ public class ResolveMerger extends ThreeWayMerger {
protected static final int T_FILE = 4;
/**
- * Builder to update the cache during this merge.
+ * Handler for repository I/O actions.
*
- * @since 3.4
+ * @since 6.3
*/
- protected DirCacheBuilder builder;
+ protected WorkTreeUpdater workTreeUpdater;
/**
* merge result as tree
@@ -163,35 +755,17 @@ public class ResolveMerger extends ThreeWayMerger {
protected ObjectId resultTree;
/**
- * Paths that could not be merged by this merger because of an unsolvable
- * conflict.
- *
- * @since 3.4
- */
- protected List<String> unmergedPaths = new ArrayList<>();
-
- /**
- * Files modified during this merge operation.
- *
- * @since 3.4
- */
- protected List<String> modifiedFiles = new LinkedList<>();
-
- /**
- * If the merger has nothing to do for a file but check it out at the end of
- * the operation, it can be added here.
- *
- * @since 3.4
+ * Files modified during this operation. Note this list is only updated after a successful write.
*/
- protected Map<String, DirCacheEntry> toBeCheckedOut = new HashMap<>();
+ protected List<String> modifiedFiles = new ArrayList<>();
/**
- * Paths in this list will be deleted from the local copy at the end of the
- * operation.
+ * Paths that could not be merged by this merger because of an unsolvable
+ * conflict.
*
* @since 3.4
*/
- protected List<String> toBeDeleted = new ArrayList<>();
+ protected List<String> unmergedPaths = new ArrayList<>();
/**
* Low-level textual merge results. Will be passed on to the callers in case
@@ -227,15 +801,6 @@ public class ResolveMerger extends ThreeWayMerger {
protected boolean inCore;
/**
- * Set to true if this merger should use the default dircache of the
- * repository and should handle locking and unlocking of the dircache. If
- * this merger should work in-core or if an explicit dircache was specified
- * during construction then this field is set to false.
- * @since 3.0
- */
- protected boolean implicitDirCache;
-
- /**
* Directory cache
* @since 3.0
*/
@@ -255,36 +820,12 @@ public class ResolveMerger extends ThreeWayMerger {
protected MergeAlgorithm mergeAlgorithm;
/**
- * The {@link WorkingTreeOptions} are needed to determine line endings for
- * merged files.
- *
- * @since 4.11
- */
- protected WorkingTreeOptions workingTreeOptions;
-
- /**
- * The size limit (bytes) which controls a file to be stored in {@code Heap}
- * or {@code LocalFile} during the merge.
- */
- private int inCoreLimit;
-
- /**
* The {@link ContentMergeStrategy} to use for "resolve" and "recursive"
* merges.
*/
@NonNull
private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
- /**
- * 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,
@@ -292,13 +833,8 @@ public class ResolveMerger extends ThreeWayMerger {
return new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
}
- private static int getInCoreLimit(Config config) {
- return config.getInt(
- ConfigConstants.CONFIG_MERGE_SECTION, ConfigConstants.CONFIG_KEY_IN_CORE_LIMIT, 10 << 20);
- }
-
private static String[] defaultCommitNames() {
- return new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ return new String[]{"BASE", "OURS", "THEIRS"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
private static final Attributes NO_ATTRIBUTES = new Attributes();
@@ -315,17 +851,8 @@ public class ResolveMerger extends ThreeWayMerger {
super(local);
Config config = local.getConfig();
mergeAlgorithm = getMergeAlgorithm(config);
- inCoreLimit = getInCoreLimit(config);
commitNames = defaultCommitNames();
this.inCore = inCore;
-
- if (inCore) {
- implicitDirCache = false;
- dircache = DirCache.newInCore();
- } else {
- implicitDirCache = true;
- workingTreeOptions = local.getConfig().get(WorkingTreeOptions.KEY);
- }
}
/**
@@ -352,8 +879,6 @@ public class ResolveMerger extends ThreeWayMerger {
mergeAlgorithm = getMergeAlgorithm(config);
commitNames = defaultCommitNames();
inCore = true;
- implicitDirCache = false;
- dircache = DirCache.newInCore();
}
/**
@@ -382,81 +907,8 @@ public class ResolveMerger extends ThreeWayMerger {
/** {@inheritDoc} */
@Override
protected boolean mergeImpl() throws IOException {
- if (implicitDirCache) {
- dircache = nonNullRepo().lockDirCache();
- }
- 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();
- }
- }
- }
-
- private void checkout() throws NoWorkTreeException, IOException {
- // Iterate in reverse so that "folder/file" is deleted before
- // "folder". Otherwise this could result in a failing path because
- // of a non-empty directory, for which delete() would fail.
- for (int i = toBeDeleted.size() - 1; i >= 0; i--) {
- String fileName = toBeDeleted.get(i);
- File f = new File(nonNullRepo().getWorkTree(), fileName);
- if (!f.delete())
- if (!f.isDirectory())
- failingPaths.put(fileName,
- MergeFailureReason.COULD_NOT_DELETE);
- modifiedFiles.add(fileName);
- }
- for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
- .entrySet()) {
- DirCacheEntry cacheEntry = entry.getValue();
- if (cacheEntry.getFileMode() == FileMode.GITLINK) {
- new File(nonNullRepo().getWorkTree(), entry.getKey()).mkdirs();
- } else {
- DirCacheCheckout.checkoutEntry(db, cacheEntry, reader, false,
- checkoutMetadata.get(entry.getKey()));
- modifiedFiles.add(entry.getKey());
- }
- }
- }
-
- /**
- * Reverts the worktree after an unsuccessful merge. We know that for all
- * modified files the old content was in the old index and the index
- * contained only stage 0. In case if inCore operation just clear the
- * history of modified files.
- *
- * @throws java.io.IOException
- * @throws org.eclipse.jgit.errors.CorruptObjectException
- * @throws org.eclipse.jgit.errors.NoWorkTreeException
- * @since 3.4
- */
- protected void cleanUp() throws NoWorkTreeException,
- CorruptObjectException,
- IOException {
- if (inCore) {
- modifiedFiles.clear();
- return;
- }
-
- DirCache dc = nonNullRepo().readDirCache();
- Iterator<String> mpathsIt=modifiedFiles.iterator();
- while(mpathsIt.hasNext()) {
- String mpath = mpathsIt.next();
- DirCacheEntry entry = dc.getEntry(mpath);
- if (entry != null) {
- DirCacheCheckout.checkoutEntry(db, entry, reader, false,
- cleanupMetadata.get(mpath));
- }
- mpathsIt.remove();
- }
+ return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
+ false);
}
/**
@@ -472,18 +924,33 @@ public class ResolveMerger extends ThreeWayMerger {
private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
Instant lastMod, long len) {
if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
- DirCacheEntry e = new DirCacheEntry(path, stage);
- e.setFileMode(p.getEntryFileMode());
- e.setObjectId(p.getEntryObjectId());
- e.setLastModified(lastMod);
- e.setLength(len);
- builder.add(e);
- return e;
+ return workTreeUpdater.addExistingToIndex(p.getEntryObjectId(), path,
+ p.getEntryFileMode(), stage,
+ lastMod, (int) len);
}
return null;
}
/**
+ * Adds the conflict stages for the current path of {@link #tw} to the index
+ * builder and returns the "theirs" stage; if present.
+ *
+ * @param base
+ * of the conflict
+ * @param ours
+ * of the conflict
+ * @param theirs
+ * of the conflict
+ * @return the {@link DirCacheEntry} for the "theirs" stage, or {@code null}
+ */
+ private DirCacheEntry addConflict(CanonicalTreeParser base,
+ CanonicalTreeParser ours, CanonicalTreeParser theirs) {
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+ return add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ }
+
+ /**
* adds a entry to the index builder which is a copy of the specified
* DirCacheEntry
*
@@ -493,41 +960,8 @@ public class ResolveMerger extends ThreeWayMerger {
* @return the entry which was added to the index
*/
private DirCacheEntry keep(DirCacheEntry e) {
- DirCacheEntry newEntry = new DirCacheEntry(e.getRawPath(),
- e.getStage());
- newEntry.setFileMode(e.getFileMode());
- newEntry.setObjectId(e.getObjectId());
- newEntry.setLastModified(e.getLastModifiedInstant());
- newEntry.setLength(e.getLength());
- builder.add(newEntry);
- return newEntry;
- }
-
- /**
- * 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
- * to use for determining the metadata
- * @throws IOException
- * if the smudge filter cannot be determined
- * @since 6.1
- */
- protected void addCheckoutMetadata(Map<String, CheckoutMetadata> map,
- String path, Attributes attributes)
- throws IOException {
- if (map != null) {
- EolStreamType eol = EolStreamTypeUtil.detectStreamType(
- OperationType.CHECKOUT_OP, workingTreeOptions,
- attributes);
- CheckoutMetadata data = new CheckoutMetadata(eol,
- tw.getSmudgeCommand(attributes));
- map.put(path, data);
- }
+ return workTreeUpdater.addExistingToIndex(e.getObjectId(), e.getRawPath(), e.getFileMode(),
+ e.getStage(), e.getLastModifiedInstant(), e.getLength());
}
/**
@@ -547,14 +981,17 @@ public class ResolveMerger extends ThreeWayMerger {
protected void addToCheckout(String path, DirCacheEntry entry,
Attributes[] attributes)
throws IOException {
- toBeCheckedOut.put(path, entry);
- addCheckoutMetadata(cleanupMetadata, path, attributes[T_OURS]);
- addCheckoutMetadata(checkoutMetadata, path, attributes[T_THEIRS]);
+ EolStreamType cleanupStreamType = workTreeUpdater.detectCheckoutStreamType(attributes[T_OURS]);
+ String cleanupSmudgeCommand = tw.getSmudgeCommand(attributes[T_OURS]);
+ EolStreamType checkoutStreamType = workTreeUpdater.detectCheckoutStreamType(attributes[T_THEIRS]);
+ String checkoutSmudgeCommand = tw.getSmudgeCommand(attributes[T_THEIRS]);
+ workTreeUpdater.addToCheckout(path, entry, cleanupStreamType, cleanupSmudgeCommand,
+ checkoutStreamType, checkoutSmudgeCommand);
}
/**
* Remember a path for deletion, and remember its {@link CheckoutMetadata}
- * in case it has to be restored in {@link #cleanUp()}.
+ * in case it has to be restored in the cleanUp.
*
* @param path
* of the entry
@@ -568,10 +1005,13 @@ public class ResolveMerger extends ThreeWayMerger {
*/
protected void addDeletion(String path, boolean isFile,
Attributes attributes) throws IOException {
- toBeDeleted.add(path);
- if (isFile) {
- addCheckoutMetadata(cleanupMetadata, path, attributes);
- }
+ if (db == null || nonNullRepo().isBare() || !isFile)
+ return;
+
+ File file = new File(nonNullRepo().getWorkTree(), path);
+ EolStreamType streamType = workTreeUpdater.detectCheckoutStreamType(attributes);
+ String smudgeCommand = tw.getSmudgeCommand(attributes);
+ workTreeUpdater.deleteFile(path, file, streamType, smudgeCommand);
}
/**
@@ -615,9 +1055,6 @@ public class ResolveMerger extends ThreeWayMerger {
* @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
- * @throws org.eclipse.jgit.errors.MissingObjectException
- * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
- * @throws org.eclipse.jgit.errors.CorruptObjectException
* @throws java.io.IOException
* @since 6.1
*/
@@ -625,20 +1062,21 @@ public class ResolveMerger extends ThreeWayMerger {
CanonicalTreeParser ours, CanonicalTreeParser theirs,
DirCacheBuildIterator index, WorkingTreeIterator work,
boolean ignoreConflicts, Attributes[] attributes)
- throws MissingObjectException, IncorrectObjectTypeException,
- CorruptObjectException, IOException {
+ throws IOException {
enterSubtree = true;
final int modeO = tw.getRawMode(T_OURS);
final int modeT = tw.getRawMode(T_THEIRS);
final int modeB = tw.getRawMode(T_BASE);
boolean gitLinkMerging = isGitLink(modeO) || isGitLink(modeT)
|| isGitLink(modeB);
- if (modeO == 0 && modeT == 0 && modeB == 0)
+ if (modeO == 0 && modeT == 0 && modeB == 0) {
// File is either untracked or new, staged but uncommitted
return true;
+ }
- if (isIndexDirty())
+ if (isIndexDirty()) {
return false;
+ }
DirCacheEntry ourDce = null;
@@ -693,9 +1131,7 @@ public class ResolveMerger extends ThreeWayMerger {
// length.
// This path can be skipped on ignoreConflicts, so the caller
// could use virtual commit.
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ addConflict(base, ours, theirs);
unmergedPaths.add(tw.getPathString());
mergeResults.put(tw.getPathString(),
new MergeResult<>(Collections.emptyList()));
@@ -706,8 +1142,9 @@ public class ResolveMerger extends ThreeWayMerger {
if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
// THEIRS was not changed compared to BASE. All changes must be in
// OURS. OURS is chosen. We can keep the existing entry.
- if (ourDce != null)
+ if (ourDce != null) {
keep(ourDce);
+ }
// no checkout needed!
return true;
}
@@ -717,8 +1154,9 @@ public class ResolveMerger extends ThreeWayMerger {
// THEIRS. THEIRS is chosen.
// Check worktree before checking out THEIRS
- if (isWorktreeDirty(work, ourDce))
+ if (isWorktreeDirty(work, ourDce)) {
return false;
+ }
if (nonTree(modeT)) {
// we know about length and lastMod only after we have written
// the new content.
@@ -759,12 +1197,15 @@ public class ResolveMerger extends ThreeWayMerger {
enterSubtree = false;
return true;
}
- if (nonTree(modeB))
+ if (nonTree(modeB)) {
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
- if (nonTree(modeO))
+ }
+ if (nonTree(modeO)) {
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
- if (nonTree(modeT))
+ }
+ if (nonTree(modeT)) {
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ }
unmergedPaths.add(tw.getPathString());
enterSubtree = false;
return true;
@@ -774,8 +1215,9 @@ public class ResolveMerger extends ThreeWayMerger {
// tells us we are in a subtree because of index or working-dir).
// If they are both folders no content-merge is required - we can
// return here.
- if (!nonTree(modeO))
+ if (!nonTree(modeO)) {
return true;
+ }
// ours and theirs are both files, just fall out of the if block
// and do the content merge
@@ -794,9 +1236,7 @@ public class ResolveMerger extends ThreeWayMerger {
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
return true;
} else if (gitLinkMerging) {
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ addConflict(base, ours, theirs);
MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
base, ours, theirs);
result.setContainsConflicts(true);
@@ -806,20 +1246,18 @@ public class ResolveMerger extends ThreeWayMerger {
} else if (!attributes[T_OURS].canBeContentMerged()) {
// File marked as binary
switch (getContentMergeStrategy()) {
- case OURS:
- keep(ourDce);
- return true;
- case THEIRS:
- DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
- DirCacheEntry.STAGE_0, EPOCH, 0);
- addToCheckout(tw.getPathString(), theirEntry, attributes);
- return true;
- default:
- break;
+ case OURS:
+ keep(ourDce);
+ return true;
+ case THEIRS:
+ DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
+ DirCacheEntry.STAGE_0, EPOCH, 0);
+ addToCheckout(tw.getPathString(), theirEntry, attributes);
+ return true;
+ default:
+ break;
}
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ addConflict(base, ours, theirs);
// attribute merge issues are conflicts but not failures
unmergedPaths.add(tw.getPathString());
@@ -832,18 +1270,27 @@ public class ResolveMerger extends ThreeWayMerger {
}
MergeResult<RawText> result = null;
- try {
- result = contentMerge(base, ours, theirs, attributes,
- getContentMergeStrategy());
- } catch (BinaryBlobException e) {
+ boolean hasSymlink = FileMode.SYMLINK.equals(modeO)
+ || FileMode.SYMLINK.equals(modeT);
+ if (!hasSymlink) {
+ try {
+ result = contentMerge(base, ours, theirs, attributes,
+ getContentMergeStrategy());
+ } catch (BinaryBlobException e) {
+ // result == null
+ }
+ }
+ if (result == null) {
switch (getContentMergeStrategy()) {
case OURS:
keep(ourDce);
return true;
case THEIRS:
- DirCacheEntry theirEntry = add(tw.getRawPath(), theirs,
+ DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
- addToCheckout(tw.getPathString(), theirEntry, attributes);
+ if (e != null) {
+ addToCheckout(tw.getPathString(), e, attributes);
+ }
return true;
default:
result = new MergeResult<>(Collections.emptyList());
@@ -854,16 +1301,36 @@ public class ResolveMerger extends ThreeWayMerger {
if (ignoreConflicts) {
result.setContainsConflicts(false);
}
- updateIndex(base, ours, theirs, result, attributes[T_OURS]);
String currentPath = tw.getPathString();
+ if (hasSymlink) {
+ if (ignoreConflicts) {
+ if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)) {
+ DirCacheEntry e = add(tw.getRawPath(), theirs,
+ DirCacheEntry.STAGE_0, EPOCH, 0);
+ addToCheckout(currentPath, e, attributes);
+ } else {
+ keep(ourDce);
+ }
+ } else {
+ // Record the conflict
+ DirCacheEntry e = addConflict(base, ours, theirs);
+ mergeResults.put(currentPath, result);
+ // If theirs is a file, check it out. In link/file
+ // conflicts, C git prefers the file.
+ if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)
+ && e != null) {
+ addToCheckout(currentPath, e, attributes);
+ }
+ }
+ } else {
+ updateIndex(base, ours, theirs, result, attributes[T_OURS]);
+ }
if (result.containsConflicts() && !ignoreConflicts) {
unmergedPaths.add(currentPath);
}
- modifiedFiles.add(currentPath);
- addCheckoutMetadata(cleanupMetadata, currentPath,
- attributes[T_OURS]);
- addCheckoutMetadata(checkoutMetadata, currentPath,
- attributes[T_THEIRS]);
+ workTreeUpdater.markAsModified(currentPath);
+ // Entry is null - only adds the metadata.
+ addToCheckout(currentPath, null, attributes);
} else if (modeO != modeT) {
// OURS or THEIRS has been deleted
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
@@ -871,40 +1338,58 @@ public class ResolveMerger extends ThreeWayMerger {
if (gitLinkMerging && ignoreConflicts) {
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0);
} else if (gitLinkMerging) {
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ addConflict(base, ours, theirs);
MergeResult<SubmoduleConflict> result = createGitLinksMergeResult(
base, ours, theirs);
result.setContainsConflicts(true);
mergeResults.put(tw.getPathString(), result);
unmergedPaths.add(tw.getPathString());
} else {
+ boolean isSymLink = ((modeO | modeT)
+ & FileMode.TYPE_MASK) == FileMode.TYPE_SYMLINK;
// Content merge strategy does not apply to delete-modify
// conflicts!
MergeResult<RawText> result;
- try {
- result = contentMerge(base, ours, theirs, attributes,
- ContentMergeStrategy.CONFLICT);
- } catch (BinaryBlobException e) {
+ if (isSymLink) {
+ // No need to do a content merge
result = new MergeResult<>(Collections.emptyList());
result.setContainsConflicts(true);
+ } else {
+ try {
+ result = contentMerge(base, ours, theirs,
+ attributes, ContentMergeStrategy.CONFLICT);
+ } catch (BinaryBlobException e) {
+ result = new MergeResult<>(Collections.emptyList());
+ result.setContainsConflicts(true);
+ }
}
if (ignoreConflicts) {
- // In case a conflict is detected the working tree file
- // is again filled with new content (containing conflict
- // markers). But also stage 0 of the index is filled
- // with that content.
result.setContainsConflicts(false);
- updateIndex(base, ours, theirs, result,
- attributes[T_OURS]);
+ if (isSymLink) {
+ if (modeO != 0) {
+ keep(ourDce);
+ } else {
+ // Check out theirs
+ if (isWorktreeDirty(work, ourDce)) {
+ return false;
+ }
+ DirCacheEntry e = add(tw.getRawPath(), theirs,
+ DirCacheEntry.STAGE_0, EPOCH, 0);
+ if (e != null) {
+ addToCheckout(tw.getPathString(), e,
+ attributes);
+ }
+ }
+ } else {
+ // In case a conflict is detected the working tree
+ // file is again filled with new content (containing
+ // conflict markers). But also stage 0 of the index
+ // is filled with that content.
+ updateIndex(base, ours, theirs, result,
+ attributes[T_OURS]);
+ }
} else {
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
- 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH,
- 0);
- DirCacheEntry e = add(tw.getRawPath(), theirs,
- DirCacheEntry.STAGE_3, EPOCH, 0);
+ DirCacheEntry e = addConflict(base, ours, theirs);
// OURS was deleted checkout THEIRS
if (modeO == 0) {
@@ -975,8 +1460,9 @@ public class ResolveMerger extends ThreeWayMerger {
}
private boolean isIndexDirty() {
- if (inCore)
+ if (inCore) {
return false;
+ }
final int modeI = tw.getRawMode(T_INDEX);
final int modeO = tw.getRawMode(T_OURS);
@@ -984,37 +1470,42 @@ public class ResolveMerger extends ThreeWayMerger {
// Index entry has to match ours to be considered clean
final boolean isDirty = nonTree(modeI)
&& !(modeO == modeI && tw.idEqual(T_INDEX, T_OURS));
- if (isDirty)
+ if (isDirty) {
failingPaths
.put(tw.getPathString(), MergeFailureReason.DIRTY_INDEX);
+ }
return isDirty;
}
private boolean isWorktreeDirty(WorkingTreeIterator work,
DirCacheEntry ourDce) throws IOException {
- if (work == null)
+ if (work == null) {
return false;
+ }
final int modeF = tw.getRawMode(T_FILE);
final int modeO = tw.getRawMode(T_OURS);
// Worktree entry has to match ours to be considered clean
boolean isDirty;
- if (ourDce != null)
+ if (ourDce != null) {
isDirty = work.isModified(ourDce, true, reader);
- else {
+ } else {
isDirty = work.isModeDifferent(modeO);
- if (!isDirty && nonTree(modeF))
+ if (!isDirty && nonTree(modeF)) {
isDirty = !tw.idEqual(T_FILE, T_OURS);
+ }
}
// Ignore existing empty directories
if (isDirty && modeF == FileMode.TYPE_TREE
- && modeO == FileMode.TYPE_MISSING)
+ && modeO == FileMode.TYPE_MISSING) {
isDirty = false;
- if (isDirty)
+ }
+ if (isDirty) {
failingPaths.put(tw.getPathString(),
MergeFailureReason.DIRTY_WORKTREE);
+ }
return isDirty;
}
@@ -1029,14 +1520,12 @@ public class ResolveMerger extends ThreeWayMerger {
* @param theirs
* @param result
* @param attributes
- * @throws FileNotFoundException
* @throws IOException
*/
private void updateIndex(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
MergeResult<RawText> result, Attributes attributes)
- throws FileNotFoundException,
- IOException {
+ throws IOException {
TemporaryBuffer rawMerged = null;
try {
rawMerged = doMerge(result);
@@ -1046,30 +1535,26 @@ public class ResolveMerger extends ThreeWayMerger {
// A conflict occurred, the file will contain conflict markers
// the index will be populated with the three stages and the
// workdir (if used) contains the halfway merged content.
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ addConflict(base, ours, theirs);
mergeResults.put(tw.getPathString(), result);
return;
}
// No conflict occurred, the file will contain fully merged content.
// The index will be populated with the new merged version.
- DirCacheEntry dce = new DirCacheEntry(tw.getPathString());
-
+ Instant lastModified = mergedFile == null ? null
+ : nonNullRepo().getFS().lastModifiedInstant(mergedFile);
// Set the mode for the new content. Fall back to REGULAR_FILE if
// we can't merge modes of OURS and THEIRS.
int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1),
tw.getRawMode(2));
- dce.setFileMode(newMode == FileMode.MISSING.getBits()
- ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode));
- if (mergedFile != null) {
- dce.setLastModified(
- nonNullRepo().getFS().lastModifiedInstant(mergedFile));
- dce.setLength((int) mergedFile.length());
- }
- dce.setObjectId(insertMergeResult(rawMerged, attributes));
- builder.add(dce);
+ FileMode mode = newMode == FileMode.MISSING.getBits()
+ ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode);
+ workTreeUpdater.insertToIndex(rawMerged.openInputStream(),
+ tw.getPathString().getBytes(UTF_8), mode,
+ DirCacheEntry.STAGE_0, lastModified,
+ (int) rawMerged.length(),
+ attributes.get(Constants.ATTR_MERGE));
} finally {
if (rawMerged != null) {
rawMerged.destroy();
@@ -1085,34 +1570,28 @@ public class ResolveMerger extends ThreeWayMerger {
* @param attributes
* the files .gitattributes entries
* @return the working tree file to which the merged content was written.
- * @throws FileNotFoundException
* @throws IOException
*/
private File writeMergedFile(TemporaryBuffer rawMerged,
Attributes attributes)
- throws FileNotFoundException, IOException {
+ throws IOException {
File workTree = nonNullRepo().getWorkTree();
FS fs = nonNullRepo().getFS();
File of = new File(workTree, tw.getPathString());
File parentFolder = of.getParentFile();
+ EolStreamType eol = workTreeUpdater.detectCheckoutStreamType(attributes);
if (!fs.exists(parentFolder)) {
parentFolder.mkdirs();
}
- EolStreamType streamType = EolStreamTypeUtil.detectStreamType(
- OperationType.CHECKOUT_OP, workingTreeOptions,
- attributes);
- try (OutputStream os = EolStreamTypeUtil.wrapOutputStream(
- new BufferedOutputStream(new FileOutputStream(of)),
- streamType)) {
- rawMerged.writeTo(os, null);
- }
+ workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
+ eol, tw.getSmudgeCommand(attributes), of.getPath(), of);
return of;
}
private TemporaryBuffer doMerge(MergeResult<RawText> result)
throws IOException {
TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(
- db != null ? nonNullRepo().getDirectory() : null, inCoreLimit);
+ db != null ? nonNullRepo().getDirectory() : null, workTreeUpdater.getInCoreFileSizeLimit());
boolean success = false;
try {
new MergeFormatter().formatMerge(buf, result,
@@ -1127,16 +1606,6 @@ public class ResolveMerger extends ThreeWayMerger {
return buf;
}
- private ObjectId insertMergeResult(TemporaryBuffer buf,
- Attributes attributes) throws IOException {
- InputStream in = buf.openInputStream();
- try (LfsInputStream is = LfsFactory.getInstance().applyCleanFilter(
- getRepository(), in,
- buf.length(), attributes.get(Constants.ATTR_MERGE))) {
- return getObjectInserter().insert(OBJ_BLOB, is.getLength(), is);
- }
- }
-
/**
* Try to merge filemodes. If only ours or theirs have changed the mode
* (compared to base) we choose that one. If ours and theirs have equal
@@ -1154,22 +1623,26 @@ public class ResolveMerger extends ThreeWayMerger {
* conflict
*/
private int mergeFileModes(int modeB, int modeO, int modeT) {
- if (modeO == modeT)
+ if (modeO == modeT) {
return modeO;
- if (modeB == modeO)
+ }
+ if (modeB == modeO) {
// Base equal to Ours -> chooses Theirs if that is not missing
return (modeT == FileMode.MISSING.getBits()) ? modeO : modeT;
- if (modeB == modeT)
+ }
+ if (modeB == modeT) {
// Base equal to Theirs -> chooses Ours if that is not missing
return (modeO == FileMode.MISSING.getBits()) ? modeT : modeO;
+ }
return FileMode.MISSING.getBits();
}
private RawText getRawText(ObjectId id,
Attributes attributes)
throws IOException, BinaryBlobException {
- if (id.equals(ObjectId.zeroId()))
- return new RawText(new byte[] {});
+ if (id.equals(ObjectId.zeroId())) {
+ return new RawText(new byte[]{});
+ }
ObjectLoader loader = LfsFactory.getInstance().applySmudgeFilter(
getRepository(), reader.open(id, OBJ_BLOB),
@@ -1233,7 +1706,7 @@ public class ResolveMerger extends ThreeWayMerger {
* superset of the files listed by {@link #getUnmergedPaths()}.
*/
public List<String> getModifiedFiles() {
- return modifiedFiles;
+ return workTreeUpdater != null ? workTreeUpdater.getModifiedFiles() : modifiedFiles;
}
/**
@@ -1247,7 +1720,7 @@ public class ResolveMerger extends ThreeWayMerger {
* for this path.
*/
public Map<String, DirCacheEntry> getToBeCheckedOut() {
- return toBeCheckedOut;
+ return workTreeUpdater.getToBeCheckedOut();
}
/**
@@ -1297,7 +1770,6 @@ public class ResolveMerger extends ThreeWayMerger {
*/
public void setDirCache(DirCache dc) {
this.dircache = dc;
- implicitDirCache = false;
}
/**
@@ -1352,53 +1824,48 @@ public class ResolveMerger extends ThreeWayMerger {
protected boolean mergeTrees(AbstractTreeIterator baseTree,
RevTree headTree, RevTree mergeTree, boolean ignoreConflicts)
throws IOException {
+ try {
+ workTreeUpdater = inCore ?
+ WorkTreeUpdater.createInCoreWorkTreeUpdater(db, dircache, getObjectInserter()) :
+ WorkTreeUpdater.createWorkTreeUpdater(db, dircache);
+ dircache = workTreeUpdater.getLockedDirCache();
+ tw = new NameConflictTreeWalk(db, reader);
+
+ tw.addTree(baseTree);
+ tw.setHead(tw.addTree(headTree));
+ tw.addTree(mergeTree);
+ DirCacheBuildIterator buildIt = workTreeUpdater.createDirCacheBuildIterator();
+ int dciPos = tw.addTree(buildIt);
+ if (workingTreeIterator != null) {
+ tw.addTree(workingTreeIterator);
+ workingTreeIterator.setDirCacheIterator(tw, dciPos);
+ } else {
+ tw.setFilter(TreeFilter.ANY_DIFF);
+ }
- builder = dircache.builder();
- DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
-
- tw = new NameConflictTreeWalk(db, reader);
- tw.addTree(baseTree);
- tw.setHead(tw.addTree(headTree));
- tw.addTree(mergeTree);
- int dciPos = tw.addTree(buildIt);
- if (workingTreeIterator != null) {
- tw.addTree(workingTreeIterator);
- workingTreeIterator.setDirCacheIterator(tw, dciPos);
- } else {
- tw.setFilter(TreeFilter.ANY_DIFF);
- }
+ if (!mergeTreeWalk(tw, ignoreConflicts)) {
+ return false;
+ }
- if (!mergeTreeWalk(tw, ignoreConflicts)) {
+ workTreeUpdater.writeWorkTreeChanges(true);
+ if (getUnmergedPaths().isEmpty() && !failed()) {
+ WorkTreeUpdater.Result result = workTreeUpdater.writeIndexChanges();
+ resultTree = result.getTreeId();
+ modifiedFiles = result.getModifiedFiles();
+ for (String f : result.getFailedToDelete()) {
+ failingPaths.put(f, MergeFailureReason.COULD_NOT_DELETE);
+ }
+ return result.getFailedToDelete().isEmpty();
+ }
+ resultTree = null;
return false;
- }
-
- if (!inCore) {
- // No problem found. The only thing left to be done is to
- // checkout all files from "theirs" which have been selected to
- // go into the new index.
- checkout();
-
- // All content-merges are successfully done. If we can now write the
- // new index we are on quite safe ground. Even if the checkout of
- // files coming from "theirs" fails the user can work around such
- // failures by checking out the index again.
- if (!builder.commit()) {
- cleanUp();
- throw new IndexWriteException();
+ } finally {
+ if(modifiedFiles.isEmpty()) {
+ modifiedFiles = workTreeUpdater.getModifiedFiles();
}
- builder = null;
-
- } else {
- builder.finish();
- builder = null;
+ workTreeUpdater.close();
+ workTreeUpdater = null;
}
-
- if (getUnmergedPaths().isEmpty() && !failed()) {
- resultTree = dircache.writeTree(getObjectInserter());
- return true;
- }
- resultTree = null;
- return false;
}
/**
@@ -1419,8 +1886,8 @@ public class ResolveMerger extends ThreeWayMerger {
boolean hasAttributeNodeProvider = treeWalk
.getAttributesNodeProvider() != null;
while (treeWalk.next()) {
- Attributes[] attributes = { NO_ATTRIBUTES, NO_ATTRIBUTES,
- NO_ATTRIBUTES };
+ Attributes[] attributes = {NO_ATTRIBUTES, NO_ATTRIBUTES,
+ NO_ATTRIBUTES};
if (hasAttributeNodeProvider) {
attributes[T_BASE] = treeWalk.getAttributes(T_BASE);
attributes[T_OURS] = treeWalk.getAttributes(T_OURS);
@@ -1434,11 +1901,12 @@ public class ResolveMerger extends ThreeWayMerger {
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
WorkingTreeIterator.class) : null,
ignoreConflicts, attributes)) {
- cleanUp();
+ workTreeUpdater.revertModifiedFiles();
return false;
}
- if (treeWalk.isSubtree() && enterSubtree)
+ if (treeWalk.isSubtree() && enterSubtree) {
treeWalk.enterSubtree();
+ }
}
return true;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
index e0c7bdd396..dda108bc69 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
@@ -66,7 +66,7 @@ abstract class AbstractRevQueue extends Generator {
* flag that controls admission to the queue.
*/
public final void addParents(RevCommit c, RevFlag queueControl) {
- final RevCommit[] pList = c.parents;
+ final RevCommit[] pList = c.getParents();
if (pList == null) {
return;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java
index 8b78d062b5..bdf63ff10b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java
@@ -76,11 +76,12 @@ class BoundaryGenerator extends Generator {
IncorrectObjectTypeException, IOException {
RevCommit c = source.next();
if (c != null) {
- for (int i = 0; i < c.parents.length; i++) {
+ int n = c.getParentCount();
+ for (int i = 0; i < n; i++) {
if (firstParent && i > 0) {
break;
}
- RevCommit p = c.parents[i];
+ RevCommit p = c.getParent(i);
if ((p.flags & UNINTERESTING) != 0) {
held.add(p);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
index 3a2cb8b0f9..ec0824cb0b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
@@ -164,11 +164,12 @@ class DepthGenerator extends Generator {
int newDepth = c.depth + 1;
- for (int i = 0; i < c.parents.length; i++) {
+ int n = c.getParentCount();
+ for (int i = 0; i < n; i++) {
if (firstParent && i > 0) {
break;
}
- RevCommit p = c.parents[i];
+ RevCommit p = c.getParent(i);
DepthWalk.Commit dp = (DepthWalk.Commit) p;
// If no depth has been assigned to this commit, assign
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
index c0fea75777..a213dd47c6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
@@ -114,7 +114,7 @@ class MergeBaseGenerator extends Generator {
return null;
}
- for (RevCommit p : c.parents) {
+ for (RevCommit p : c.getParents()) {
if ((p.flags & IN_PENDING) != 0)
continue;
if ((p.flags & PARSED) == 0)
@@ -180,7 +180,7 @@ class MergeBaseGenerator extends Generator {
private void carryOntoHistoryInnerLoop(RevCommit c, int carry) {
for (;;) {
- RevCommit[] parents = c.parents;
+ RevCommit[] parents = c.getParents();
if (parents == null || parents.length == 0) {
break;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
index add387de03..a49f787316 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java
@@ -108,8 +108,9 @@ class PendingGenerator extends Generator {
produce = filter.include(walker, c);
}
- for (int i = 0; i < c.parents.length; i++) {
- RevCommit p = c.parents[i];
+ int parentCount = c.getParentCount();
+ for (int i = 0; i < parentCount; i++) {
+ RevCommit p = c.getParent(i);
// If the commit is uninteresting, don't try to prune
// parents because we want the maximal uninteresting set.
if (firstParent && i > 0 && (c.flags & UNINTERESTING) == 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index 82725f3db9..6b644cef90 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -36,6 +36,11 @@ import org.eclipse.jgit.util.StringUtils;
/**
* A commit reference to a commit in the DAG.
+ *
+ * The state of the RevCommit isn't populated until the commit is parsed. The
+ * newly created RevCommit is unparsed and only has an objectId reference. Other
+ * states like parents, trees, commit ident, commit message, etc. are
+ * populated/available when the commit is parsed.
*/
public class RevCommit extends RevObject {
private static final int STACK_DEPTH = 500;
@@ -102,7 +107,14 @@ public class RevCommit extends RevObject {
private RevTree tree;
- RevCommit[] parents;
+ /**
+ * Avoid accessing this field directly. Use method
+ * {@link RevCommit#getParents()} instead. RevCommit does not allow parents
+ * to be overridden and altering parent(s) is not supported.
+ *
+ * @since 6.3
+ */
+ protected RevCommit[] parents;
int commitTime; // An int here for performance, overflows in 2038
@@ -146,7 +158,7 @@ public class RevCommit extends RevObject {
tree = walk.lookupTree(idBuffer);
int ptr = 46;
- if (parents == null) {
+ if (getParents() == null) {
RevCommit[] pList = new RevCommit[1];
int nParents = 0;
for (;;) {
@@ -210,8 +222,8 @@ public class RevCommit extends RevObject {
}
private static FIFORevQueue carryFlags1(RevCommit c, int carry, int depth) {
- for(;;) {
- RevCommit[] pList = c.parents;
+ for (;;) {
+ RevCommit[] pList = c.getParents();
if (pList == null || pList.length == 0)
return null;
if (pList.length != 1) {
@@ -259,7 +271,7 @@ public class RevCommit extends RevObject {
// Commits in q have non-null parent arrays and have set all
// flags in carry. This loop finishes copying over the graph.
for (RevCommit c; (c = q.next()) != null;) {
- for (RevCommit p : c.parents)
+ for (RevCommit p : c.getParents())
carryOneStep(q, carry, p);
}
}
@@ -267,7 +279,7 @@ public class RevCommit extends RevObject {
private static void carryOneStep(FIFORevQueue q, int carry, RevCommit c) {
if ((c.flags & carry) != carry) {
c.flags |= carry;
- if (c.parents != null)
+ if (c.getParents() != null)
q.add(c);
}
}
@@ -313,8 +325,8 @@ public class RevCommit extends RevObject {
*
* @return number of parents; always a positive value but can be 0.
*/
- public final int getParentCount() {
- return parents.length;
+ public int getParentCount() {
+ return parents == null ? 0 : parents.length;
}
/**
@@ -327,7 +339,7 @@ public class RevCommit extends RevObject {
* @throws java.lang.ArrayIndexOutOfBoundsException
* an invalid parent index was specified.
*/
- public final RevCommit getParent(int nth) {
+ public RevCommit getParent(int nth) {
return parents[nth];
}
@@ -341,7 +353,7 @@ public class RevCommit extends RevObject {
*
* @return the array of parents.
*/
- public final RevCommit[] getParents() {
+ public RevCommit[] getParents() {
return parents;
}
@@ -353,9 +365,9 @@ public class RevCommit extends RevObject {
* this buffer should be very careful to ensure they do not modify its
* contents during their use of it.
*
- * @return the raw unparsed commit body. This is <b>NOT A COPY</b>.
- * Altering the contents of this buffer may alter the walker's
- * knowledge of this commit, and the results it produces.
+ * @return the raw unparsed commit body. This is <b>NOT A COPY</b>. Altering
+ * the contents of this buffer may alter the walker's knowledge of
+ * this commit, and the results it produces.
*/
public final byte[] getRawBuffer() {
return buffer;
@@ -380,7 +392,7 @@ public class RevCommit extends RevObject {
*/
public final byte[] getRawGpgSignature() {
final byte[] raw = buffer;
- final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'};
+ final byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
final int start = RawParseUtils.headerStart(header, raw, 0);
if (start < 0) {
return null;
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 a25948e50b..8da36c5243 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -154,7 +154,9 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
*/
static final int TREE_REV_FILTER_APPLIED = 1 << 7;
- /** Number of flag bits we keep internal for our own use. See above flags. */
+ /**
+ * Number of flag bits we keep internal for our own use. See above flags.
+ */
static final int RESERVED_FLAGS = 8;
private static final int APP_FLAGS = -1 & ~((1 << RESERVED_FLAGS) - 1);
@@ -196,9 +198,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
boolean shallowCommitsInitialized;
private enum GetMergedIntoStrategy {
- RETURN_ON_FIRST_FOUND,
- RETURN_ON_FIRST_NOT_FOUND,
- EVALUATE_ALL
+ RETURN_ON_FIRST_FOUND, RETURN_ON_FIRST_NOT_FOUND, EVALUATE_ALL
}
/**
@@ -219,8 +219,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
*
* @param or
* the reader the walker will obtain data from. The reader is not
- * closed when the walker is closed (but is closed by {@link
- * #dispose()}.
+ * closed when the walker is closed (but is closed by
+ * {@link #dispose()}.
*/
public RevWalk(ObjectReader or) {
this(or, false);
@@ -381,9 +381,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* @throws java.io.IOException
* a pack file or loose object could not be read.
*/
- public void markUninteresting(RevCommit c)
- throws MissingObjectException, IncorrectObjectTypeException,
- IOException {
+ public void markUninteresting(RevCommit c) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
c.flags |= UNINTERESTING;
carryFlagsImpl(c);
markStart(c);
@@ -392,8 +391,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
/**
* Determine if a commit is reachable from another commit.
* <p>
- * A commit <code>base</code> is an ancestor of <code>tip</code> if we
- * can find a path of commits that leads from <code>tip</code> and ends at
+ * A commit <code>base</code> is an ancestor of <code>tip</code> if we can
+ * find a path of commits that leads from <code>tip</code> and ends at
* <code>base</code>.
* <p>
* This utility function resets the walker, inserts the two supplied
@@ -462,7 +461,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* @since 5.12
*/
public List<Ref> getMergedInto(RevCommit commit, Collection<Ref> refs)
- throws IOException{
+ throws IOException {
return getMergedInto(commit, refs, NullProgressMonitor.INSTANCE);
}
@@ -486,9 +485,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* @since 5.12
*/
public List<Ref> getMergedInto(RevCommit commit, Collection<Ref> refs,
- ProgressMonitor monitor) throws IOException{
- return getMergedInto(commit, refs,
- GetMergedIntoStrategy.EVALUATE_ALL,
+ ProgressMonitor monitor) throws IOException {
+ return getMergedInto(commit, refs, GetMergedIntoStrategy.EVALUATE_ALL,
monitor);
}
@@ -531,12 +529,11 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
throws IOException {
return getMergedInto(commit, refs,
GetMergedIntoStrategy.RETURN_ON_FIRST_NOT_FOUND,
- NullProgressMonitor.INSTANCE).size()
- == refs.size();
+ NullProgressMonitor.INSTANCE).size() == refs.size();
}
private List<Ref> getMergedInto(RevCommit needle, Collection<Ref> haystacks,
- Enum returnStrategy, ProgressMonitor monitor) throws IOException {
+ Enum returnStrategy, ProgressMonitor monitor) throws IOException {
List<Ref> result = new ArrayList<>();
List<RevCommit> uninteresting = new ArrayList<>();
List<RevCommit> marked = new ArrayList<>();
@@ -547,7 +544,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
reset(~freeFlags & APP_FLAGS);
filter = RevFilter.ALL;
treeFilter = TreeFilter.ALL;
- for (Ref r: haystacks) {
+ for (Ref r : haystacks) {
if (monitor.isCancelled()) {
return result;
}
@@ -574,7 +571,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
break;
}
}
- if(!commitFound){
+ if (!commitFound) {
markUninteresting(c);
uninteresting.add(c);
if (returnStrategy == GetMergedIntoStrategy.RETURN_ON_FIRST_NOT_FOUND) {
@@ -990,9 +987,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* a pack file or loose object could not be read.
*/
@NonNull
- public RevCommit parseCommit(AnyObjectId id)
- throws MissingObjectException, IncorrectObjectTypeException,
- IOException {
+ public RevCommit parseCommit(AnyObjectId id) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
RevObject c = peel(parseAny(id));
if (!(c instanceof RevCommit))
throw new IncorrectObjectTypeException(id.toObjectId(),
@@ -1018,9 +1014,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* a pack file or loose object could not be read.
*/
@NonNull
- public RevTree parseTree(AnyObjectId id)
- throws MissingObjectException, IncorrectObjectTypeException,
- IOException {
+ public RevTree parseTree(AnyObjectId id) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
RevObject c = peel(parseAny(id));
final RevTree t;
@@ -1274,8 +1269,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* @throws java.io.IOException
* a pack file or loose object could not be read.
*/
- public RevObject peel(RevObject obj) throws MissingObjectException,
- IOException {
+ public RevObject peel(RevObject obj)
+ throws MissingObjectException, IOException {
while (obj instanceof RevTag) {
parseHeaders(obj);
obj = ((RevTag) obj).getObject();
@@ -1304,9 +1299,9 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
int allocFlag() {
if (freeFlags == 0)
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().flagsAlreadyCreated,
- Integer.valueOf(32 - RESERVED_FLAGS)));
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().flagsAlreadyCreated,
+ Integer.valueOf(32 - RESERVED_FLAGS)));
final int m = Integer.lowestOneBit(freeFlags);
freeFlags &= ~m;
return m;
@@ -1323,9 +1318,11 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
*/
public void carry(RevFlag flag) {
if ((freeFlags & flag.mask) != 0)
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagIsDisposed, flag.name));
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().flagIsDisposed, flag.name));
if (flag.walker != this)
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagNotFromThis, flag.name));
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().flagNotFromThis, flag.name));
carryFlags |= flag.mask;
}
@@ -1359,9 +1356,11 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
*/
public final void retainOnReset(RevFlag flag) {
if ((freeFlags & flag.mask) != 0)
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagIsDisposed, flag.name));
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().flagIsDisposed, flag.name));
if (flag.walker != this)
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagNotFromThis, flag.name));
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().flagNotFromThis, flag.name));
retainOnReset |= flag.mask;
}
@@ -1496,9 +1495,9 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
final RevCommit c = q.next();
if (c == null)
break;
- if (c.parents == null)
+ if (c.getParents() == null)
continue;
- for (RevCommit p : c.parents) {
+ for (RevCommit p : c.getParents()) {
if ((p.flags & clearFlags) == 0)
continue;
p.flags &= retainFlags;
@@ -1538,7 +1537,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* Like {@link #next()}, but if a checked exception is thrown during the
* walk it is rethrown as a {@link RevWalkException}.
*
- * @throws RevWalkException if an {@link IOException} was thrown.
+ * @throws RevWalkException
+ * if an {@link IOException} was thrown.
* @return next most recent commit; null if traversal is over.
*/
@Nullable
@@ -1598,7 +1598,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
protected void assertNotStarted() {
if (isNotStarted())
return;
- throw new IllegalStateException(JGitText.get().outputHasAlreadyBeenStarted);
+ throw new IllegalStateException(
+ JGitText.get().outputHasAlreadyBeenStarted);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
index 1adef07ad9..2c88bb872e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
@@ -72,7 +72,7 @@ class RewriteGenerator extends Generator {
applyFilterToParents(c);
boolean rewrote = false;
- final RevCommit[] pList = c.parents;
+ final RevCommit[] pList = c.getParents();
final int nParents = pList.length;
for (int i = 0; i < nParents; i++) {
final RevCommit oldp = pList[i];
@@ -108,7 +108,7 @@ class RewriteGenerator extends Generator {
private void applyFilterToParents(RevCommit c)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
- for (RevCommit parent : c.parents) {
+ for (RevCommit parent : c.getParents()) {
while ((parent.flags & RevWalk.TREE_REV_FILTER_APPLIED) == 0) {
RevCommit n = source.next();
@@ -130,7 +130,7 @@ class RewriteGenerator extends Generator {
IncorrectObjectTypeException, IOException {
for (;;) {
- if (p.parents.length > 1) {
+ if (p.getParentCount() > 1) {
// This parent is a merge, so keep it.
//
return p;
@@ -150,15 +150,15 @@ class RewriteGenerator extends Generator {
return p;
}
- if (p.parents.length == 0) {
+ if (p.getParentCount() == 0) {
// We can't go back any further, other than to
// just delete the parent entirely.
//
return null;
}
- applyFilterToParents(p.parents[0]);
- p = p.parents[0];
+ applyFilterToParents(p.getParent(0));
+ p = p.getParent(0);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java
index 4f6d417ed1..452545a04e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java
@@ -48,7 +48,7 @@ class TopoNonIntermixSortGenerator extends Generator {
break;
}
if ((c.flags & TOPO_QUEUED) == 0) {
- for (RevCommit p : c.parents) {
+ for (RevCommit p : c.getParents()) {
p.inDegree++;
if (firstParent) {
@@ -94,7 +94,7 @@ class TopoNonIntermixSortGenerator extends Generator {
continue;
}
- for (RevCommit p : c.parents) {
+ for (RevCommit p : c.getParents()) {
if (--p.inDegree == 0 && (p.flags & TOPO_QUEUED) != 0) {
// The parent has no unproduced interesting children. unpop
// the parent so it goes right behind this child. This means
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
index 7a5db43a7a..4739f78fc0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
@@ -47,7 +47,7 @@ class TopoSortGenerator extends Generator {
if (c == null) {
break;
}
- for (RevCommit p : c.parents) {
+ for (RevCommit p : c.getParents()) {
p.inDegree++;
if (firstParent) {
break;
@@ -86,7 +86,7 @@ class TopoSortGenerator extends Generator {
// All of our children have already produced,
// so it is OK for us to produce now as well.
//
- for (RevCommit p : c.parents) {
+ for (RevCommit p : c.getParents()) {
if (--p.inDegree == 0 && (p.flags & TOPO_DELAY) != 0) {
// This parent tried to come before us, but we are
// his last child. unpop the parent so it goes right
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
index 92d72268d1..f921449ffa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
@@ -44,6 +44,7 @@ public class TreeRevFilter extends RevFilter {
private static final int FILTER_APPLIED = RevWalk.TREE_REV_FILTER_APPLIED;
private final int rewriteFlag;
+
private final TreeWalk pathFilter;
/**
@@ -62,10 +63,9 @@ public class TreeRevFilter extends RevFilter {
this(walker, t, 0);
}
-
/**
- * Create a filter for the first phase of a parent-rewriting limited revision
- * walk.
+ * Create a filter for the first phase of a parent-rewriting limited
+ * revision walk.
* <p>
* This filter is ANDed to evaluate before all other filters and ties the
* configured {@link TreeFilter} into the revision walking process.
@@ -79,8 +79,8 @@ public class TreeRevFilter extends RevFilter {
* @param walker
* walker used for reading trees.
* @param t
- * filter to compare against any changed paths in each commit. If a
- * {@link FollowFilter}, will be replaced with a new filter
+ * filter to compare against any changed paths in each commit. If
+ * a {@link FollowFilter}, will be replaced with a new filter
* following new paths after a rename.
* @param rewriteFlag
* flag to color commits to be removed from the simplified DAT.
@@ -106,12 +106,12 @@ public class TreeRevFilter extends RevFilter {
c.flags |= FILTER_APPLIED;
// Reset the tree filter to scan this commit and parents.
//
- RevCommit[] pList = c.parents;
+ RevCommit[] pList = c.getParents();
int nParents = pList.length;
TreeWalk tw = pathFilter;
ObjectId[] trees = new ObjectId[nParents + 1];
for (int i = 0; i < nParents; i++) {
- RevCommit p = c.parents[i];
+ RevCommit p = c.getParent(i);
if ((p.flags & PARSED) == 0) {
p.parseHeaders(walker);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index 3f167ccce2..be36d2b834 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -12,16 +12,30 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_END;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_UNSHALLOW;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Set;
import org.eclipse.jgit.errors.PackProtocolException;
@@ -32,6 +46,7 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectDatabase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ProgressMonitor;
@@ -76,7 +91,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
/**
* Maximum number of 'have' lines to send before giving up.
* <p>
- * During {@link #negotiate(ProgressMonitor)} we send at most this many
+ * During {@link #negotiate(ProgressMonitor, boolean, Set)} we send at most this many
* commits to the remote peer as 'have' lines without an ACK response before
* we give up.
*/
@@ -210,6 +225,12 @@ public abstract class BasePackFetchConnection extends BasePackConnection
private int maxHaves;
+ private Integer depth;
+
+ private Instant deepenSince;
+
+ private List<String> deepenNots;
+
/**
* RPC state, if {@link BasePackConnection#statelessRPC} is true or protocol
* V2 is used.
@@ -246,6 +267,9 @@ public abstract class BasePackFetchConnection extends BasePackConnection
includeTags = transport.getTagOpt() != TagOpt.NO_TAGS;
thinPack = transport.isFetchThin();
filterSpec = transport.getFilterSpec();
+ depth = transport.getDepth();
+ deepenSince = transport.getDeepenSince();
+ deepenNots = transport.getDeepenNots();
if (local != null) {
walk = new RevWalk(local);
@@ -385,9 +409,17 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
PacketLineOut output = statelessRPC ? pckState : pckOut;
if (sendWants(want, output)) {
+ boolean mayHaveShallow = depth != null || deepenSince != null || !deepenNots.isEmpty();
+ Set<ObjectId> shallowCommits = local.getObjectDatabase().getShallowCommits();
+ if (isCapableOf(GitProtocolConstants.CAPABILITY_SHALLOW)) {
+ sendShallow(shallowCommits, output);
+ } else if (mayHaveShallow) {
+ throw new PackProtocolException(JGitText.get().shallowNotSupported);
+ }
output.end();
outNeedsEnd = false;
- negotiate(monitor);
+
+ negotiate(monitor, mayHaveShallow, shallowCommits);
clearState();
@@ -424,10 +456,18 @@ public abstract class BasePackFetchConnection extends BasePackConnection
for (String capability : getCapabilitiesV2(capabilities)) {
pckState.writeString(capability);
}
+
if (!sendWants(want, pckState)) {
// We already have everything we wanted.
return;
}
+
+ Set<ObjectId> shallowCommits = local.getObjectDatabase().getShallowCommits();
+ if (capabilities.contains(GitProtocolConstants.CAPABILITY_SHALLOW)) {
+ sendShallow(shallowCommits, pckState);
+ } else if (depth != null || deepenSince != null || !deepenNots.isEmpty()) {
+ throw new PackProtocolException(JGitText.get().shallowNotSupported);
+ }
// If we send something, we always close it properly ourselves.
outNeedsEnd = false;
@@ -455,10 +495,21 @@ public abstract class BasePackFetchConnection extends BasePackConnection
clearState();
String line = pckIn.readString();
// If we sent a done, we may have an error reply here.
- if (sentDone && line.startsWith("ERR ")) { //$NON-NLS-1$
+ if (sentDone && line.startsWith(PACKET_ERR)) {
throw new RemoteRepositoryException(uri, line.substring(4));
}
- // "shallow-info", "wanted-refs", and "packfile-uris" would have to be
+
+ if (GitProtocolConstants.SECTION_SHALLOW_INFO.equals(line)) {
+ line = handleShallowUnshallow(shallowCommits, pckIn);
+ if (!PacketLineIn.isDelimiter(line)) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().expectedGot, PACKET_DELIM,
+ line));
+ }
+ line = pckIn.readString();
+ }
+
+ // "wanted-refs" and "packfile-uris" would have to be
// handled here in that order.
if (!GitProtocolConstants.SECTION_PACKFILE.equals(line)) {
throw new PackProtocolException(
@@ -494,7 +545,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
if (c == null) {
break;
}
- output.writeString("have " + c.getId().name() + '\n'); //$NON-NLS-1$
+ output.writeString(PACKET_HAVE + c.getId().name() + '\n');
n++;
if (n % 10 == 0 && monitor.isCancelled()) {
throw new CancelledException();
@@ -505,7 +556,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
|| (fetchState.hadAcks
&& fetchState.havesWithoutAck > MAX_HAVES)
|| fetchState.havesTotal > maxHaves) {
- output.writeString("done\n"); //$NON-NLS-1$
+ output.writeString(PACKET_DONE + '\n');
output.end();
return true;
}
@@ -572,11 +623,12 @@ public abstract class BasePackFetchConnection extends BasePackConnection
if (gotReady) {
if (!PacketLineIn.isDelimiter(line)) {
throw new PackProtocolException(MessageFormat
- .format(JGitText.get().expectedGot, "0001", line)); //$NON-NLS-1$
+ .format(JGitText.get().expectedGot, PACKET_DELIM,
+ line));
}
} else if (!PacketLineIn.isEnd(line)) {
throw new PackProtocolException(MessageFormat
- .format(JGitText.get().expectedGot, "0000", line)); //$NON-NLS-1$
+ .format(JGitText.get().expectedGot, PACKET_END, line));
}
return gotReady;
}
@@ -672,21 +724,23 @@ public abstract class BasePackFetchConnection extends BasePackConnection
if (objectId == null) {
continue;
}
- try {
- if (walk.parseAny(objectId).has(REACHABLE)) {
- // We already have this object. Asking for it is
- // not a very good idea.
- //
- continue;
+ // if depth is set we need to fetch the objects even if they are already available
+ if (transport.getDepth() == null) {
+ try {
+ if (walk.parseAny(objectId).has(REACHABLE)) {
+ // We already have this object. Asking for it is
+ // not a very good idea.
+ //
+ continue;
+ }
+ } catch (IOException err) {
+ // Its OK, we don't have it, but we want to fix that
+ // by fetching the object from the other side.
}
- } catch (IOException err) {
- // Its OK, we don't have it, but we want to fix that
- // by fetching the object from the other side.
}
final StringBuilder line = new StringBuilder(46);
- line.append("want "); //$NON-NLS-1$
- line.append(objectId.name());
+ line.append(PACKET_WANT).append(objectId.name());
if (first && TransferConfig.ProtocolVersion.V0
.equals(getProtocolVersion())) {
line.append(enableCapabilities());
@@ -773,8 +827,8 @@ public abstract class BasePackFetchConnection extends BasePackConnection
return line.toString();
}
- private void negotiate(ProgressMonitor monitor) throws IOException,
- CancelledException {
+ private void negotiate(ProgressMonitor monitor, boolean mayHaveShallow, Set<ObjectId> shallowCommits)
+ throws IOException, CancelledException {
final MutableObjectId ackId = new MutableObjectId();
int resultsPending = 0;
int havesSent = 0;
@@ -795,7 +849,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
ObjectId o = c.getId();
- pckOut.writeString("have " + o.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ pckOut.writeString(PACKET_HAVE + o.name() + '\n');
havesSent++;
havesSinceLastContinue++;
@@ -898,7 +952,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
// loop above while in the middle of a request. This allows us
// to just write done immediately.
//
- pckOut.writeString("done\n"); //$NON-NLS-1$
+ pckOut.writeString(PACKET_DONE + '\n');
pckOut.flush();
}
@@ -911,6 +965,14 @@ public abstract class BasePackFetchConnection extends BasePackConnection
resultsPending++;
}
+ if (mayHaveShallow) {
+ String line = handleShallowUnshallow(shallowCommits, pckIn);
+ if (!PacketLineIn.isEnd(line)) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().expectedGot, PACKET_END, line));
+ }
+ }
+
READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) {
final AckNackResult anr = pckIn.readACK(ackId);
resultsPending--;
@@ -992,7 +1054,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
private void markCommon(RevObject obj, AckNackResult anr, boolean useState)
throws IOException {
if (useState && anr == AckNackResult.ACK_COMMON && !obj.has(STATE)) {
- pckState.writeString("have " + obj.name() + '\n'); //$NON-NLS-1$
+ pckState.writeString(PACKET_HAVE + obj.name() + '\n');
obj.add(STATE);
}
obj.add(COMMON);
@@ -1025,6 +1087,55 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
}
+ private void sendShallow(Set<ObjectId> shallowCommits, PacketLineOut output)
+ throws IOException {
+ for (ObjectId shallowCommit : shallowCommits) {
+ output.writeString(PACKET_SHALLOW + shallowCommit.name());
+ }
+
+ if (depth != null) {
+ output.writeString(PACKET_DEEPEN + depth);
+ }
+
+ if (deepenSince != null) {
+ output.writeString(
+ PACKET_DEEPEN_SINCE + deepenSince.getEpochSecond());
+ }
+
+ if (deepenNots != null) {
+ for (String deepenNotRef : deepenNots) {
+ output.writeString(PACKET_DEEPEN_NOT + deepenNotRef);
+ }
+ }
+ }
+
+ private String handleShallowUnshallow(
+ Set<ObjectId> advertisedShallowCommits, PacketLineIn input)
+ throws IOException {
+ String line = input.readString();
+ ObjectDatabase objectDatabase = local.getObjectDatabase();
+ HashSet<ObjectId> newShallowCommits = new HashSet<>(
+ advertisedShallowCommits);
+ while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) {
+ if (line.startsWith(PACKET_SHALLOW)) {
+ newShallowCommits.add(ObjectId
+ .fromString(line.substring(PACKET_SHALLOW.length())));
+ } else if (line.startsWith(PACKET_UNSHALLOW)) {
+ ObjectId unshallow = ObjectId
+ .fromString(line.substring(PACKET_UNSHALLOW.length()));
+ if (!advertisedShallowCommits.contains(unshallow)) {
+ throw new PackProtocolException(MessageFormat.format(
+ JGitText.get().notShallowedUnshallow,
+ unshallow.name()));
+ }
+ newShallowCommits.remove(unshallow);
+ }
+ line = input.readString();
+ }
+ objectDatabase.setShallowCommits(newShallowCommits);
+ return line;
+ }
+
/**
* Notification event delivered just before the pack is received from the
* network. This event can be used by RPC such as {@link org.eclipse.jgit.transport.TransportHttp} to
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index 7bface49d9..28c3b6a0fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -1,6 +1,6 @@
/*
* 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
@@ -393,7 +393,7 @@ class FetchProcess {
ow.markUninteresting(ow.parseAny(ref.getObjectId()));
ow.checkConnectivity();
}
- return true;
+ return transport.getDepth() == null; // if depth is set we need to request objects that are already available
} catch (MissingObjectException e) {
return false;
} catch (IOException e) {
@@ -516,8 +516,10 @@ class FetchProcess {
}
if (spec.getDestination() != null) {
final TrackingRefUpdate tru = createUpdate(spec, newId);
- if (newId.equals(tru.getOldObjectId()))
+ // if depth is set we need to update the ref
+ if (newId.equals(tru.getOldObjectId()) && transport.getDepth() == null) {
return;
+ }
localUpdates.add(tru);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
index 9ebc722ffe..0663c5141c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Google LLC. and others
+ * Copyright (C) 2018, 2022 Google LLC. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -35,7 +35,7 @@ abstract class FetchRequest {
final int deepenSince;
- final List<String> deepenNotRefs;
+ final List<String> deepenNots;
@Nullable
final String agent;
@@ -53,7 +53,7 @@ abstract class FetchRequest {
* the filter spec
* @param clientCapabilities
* capabilities sent in the request
- * @param deepenNotRefs
+ * @param deepenNots
* Requests that the shallow clone/fetch should be cut at these
* specific revisions instead of a depth.
* @param deepenSince
@@ -66,14 +66,14 @@ abstract class FetchRequest {
@NonNull Set<ObjectId> clientShallowCommits,
@NonNull FilterSpec filterSpec,
@NonNull Set<String> clientCapabilities, int deepenSince,
- @NonNull List<String> deepenNotRefs, @Nullable String agent) {
+ @NonNull List<String> deepenNots, @Nullable String agent) {
this.wantIds = requireNonNull(wantIds);
this.depth = depth;
this.clientShallowCommits = requireNonNull(clientShallowCommits);
this.filterSpec = requireNonNull(filterSpec);
this.clientCapabilities = requireNonNull(clientCapabilities);
this.deepenSince = deepenSince;
- this.deepenNotRefs = requireNonNull(deepenNotRefs);
+ this.deepenNots = requireNonNull(deepenNots);
this.agent = agent;
}
@@ -148,8 +148,8 @@ abstract class FetchRequest {
* @return refs received in "deepen-not" lines.
*/
@NonNull
- List<String> getDeepenNotRefs() {
- return deepenNotRefs;
+ List<String> getDeepenNots() {
+ return deepenNots;
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
index 91adb5e6ac..4decb79513 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Google LLC. and others
+ * Copyright (C) 2018, 2022 Google LLC. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -11,9 +11,10 @@ package org.eclipse.jgit.transport;
import static java.util.Objects.requireNonNull;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import org.eclipse.jgit.annotations.NonNull;
@@ -28,15 +29,20 @@ final class FetchV0Request extends FetchRequest {
FetchV0Request(@NonNull Set<ObjectId> wantIds, int depth,
@NonNull Set<ObjectId> clientShallowCommits,
@NonNull FilterSpec filterSpec,
- @NonNull Set<String> clientCapabilities, @Nullable String agent) {
+ @NonNull Set<String> clientCapabilities, int deepenSince,
+ @NonNull List<String> deepenNotRefs, @Nullable String agent) {
super(wantIds, depth, clientShallowCommits, filterSpec,
- clientCapabilities, 0, Collections.emptyList(), agent);
+ clientCapabilities, deepenSince, deepenNotRefs, agent);
}
static final class Builder {
int depth;
+ final List<String> deepenNots = new ArrayList<>();
+
+ int deepenSince;
+
final Set<ObjectId> wantIds = new HashSet<>();
final Set<ObjectId> clientShallowCommits = new HashSet<>();
@@ -68,6 +74,50 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * @return depth set in the request (via a "deepen" line). Defaulting to
+ * 0 if not set.
+ */
+ int getDepth() {
+ return depth;
+ }
+
+ /**
+ * @return true if there has been at least one "deepen not" line in the
+ * request so far
+ */
+ boolean hasDeepenNots() {
+ return !deepenNots.isEmpty();
+ }
+
+ /**
+ * @param deepenNot
+ * reference received in a "deepen not" line
+ * @return this builder
+ */
+ Builder addDeepenNot(String deepenNot) {
+ deepenNots.add(deepenNot);
+ return this;
+ }
+
+ /**
+ * @param value
+ * Unix timestamp received in a "deepen since" line
+ * @return this builder
+ */
+ Builder setDeepenSince(int value) {
+ deepenSince = value;
+ return this;
+ }
+
+ /**
+ * @return shallow since value, sent before in a "deepen since" line. 0
+ * by default.
+ */
+ int getDeepenSince() {
+ return deepenSince;
+ }
+
+ /**
* @param shallowOid
* object id received in a "shallow" line
* @return this builder
@@ -110,7 +160,7 @@ final class FetchV0Request extends FetchRequest {
FetchV0Request build() {
return new FetchV0Request(wantIds, depth, clientShallowCommits,
- filterSpec, clientCaps, agent);
+ filterSpec, clientCaps, deepenSince, deepenNots, agent);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
index 50fb9d2262..446a3bcc79 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Google LLC. and others
+ * Copyright (C) 2018, 2022 Google LLC. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -50,7 +50,7 @@ public final class FetchV2Request extends FetchRequest {
@NonNull List<String> wantedRefs,
@NonNull Set<ObjectId> wantIds,
@NonNull Set<ObjectId> clientShallowCommits, int deepenSince,
- @NonNull List<String> deepenNotRefs, int depth,
+ @NonNull List<String> deepenNots, int depth,
@NonNull FilterSpec filterSpec,
boolean doneReceived, boolean waitForDone,
@NonNull Set<String> clientCapabilities,
@@ -58,7 +58,7 @@ public final class FetchV2Request extends FetchRequest {
boolean sidebandAll, @NonNull List<String> packfileUriProtocols) {
super(wantIds, depth, clientShallowCommits, filterSpec,
clientCapabilities, deepenSince,
- deepenNotRefs, agent);
+ deepenNots, agent);
this.peerHas = requireNonNull(peerHas);
this.wantedRefs = requireNonNull(wantedRefs);
this.doneReceived = doneReceived;
@@ -140,7 +140,7 @@ public final class FetchV2Request extends FetchRequest {
final Set<ObjectId> clientShallowCommits = new HashSet<>();
- final List<String> deepenNotRefs = new ArrayList<>();
+ final List<String> deepenNots = new ArrayList<>();
final Set<String> clientCapabilities = new HashSet<>();
@@ -240,17 +240,17 @@ public final class FetchV2Request extends FetchRequest {
* @return true if there has been at least one "deepen not" line in the
* request so far
*/
- boolean hasDeepenNotRefs() {
- return !deepenNotRefs.isEmpty();
+ boolean hasDeepenNots() {
+ return !deepenNots.isEmpty();
}
/**
- * @param deepenNotRef
+ * @param deepenNot
* reference received in a "deepen not" line
* @return this builder
*/
- Builder addDeepenNotRef(String deepenNotRef) {
- deepenNotRefs.add(deepenNotRef);
+ Builder addDeepenNot(String deepenNot) {
+ deepenNots.add(deepenNot);
return this;
}
@@ -350,7 +350,7 @@ public final class FetchV2Request extends FetchRequest {
*/
FetchV2Request build() {
return new FetchV2Request(peerHas, wantedRefs, wantIds,
- clientShallowCommits, deepenSince, deepenNotRefs,
+ clientShallowCommits, deepenSince, deepenNots,
depth, filterSpec, doneReceived, waitForDone, clientCapabilities,
agent, Collections.unmodifiableList(serverOptions),
sidebandAll,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index aaa9308ac3..be14e92d07 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2008, 2013 Google Inc.
* 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
@@ -234,6 +234,13 @@ public final class GitProtocolConstants {
public static final String CAPABILITY_SERVER_OPTION = "server-option"; //$NON-NLS-1$
/**
+ * The server supports the receiving of shallow options.
+ *
+ * @since 6.3
+ */
+ public static final String CAPABILITY_SHALLOW = "shallow"; //$NON-NLS-1$
+
+ /**
* Option for passing application-specific options to the server.
*
* @since 5.2
@@ -308,6 +315,13 @@ public final class GitProtocolConstants {
public static final String SECTION_PACKFILE = "packfile"; //$NON-NLS-1$
/**
+ * Protocol V2 shallow-info section header.
+ *
+ * @since 6.3
+ */
+ public static final String SECTION_SHALLOW_INFO = "shallow-info"; //$NON-NLS-1$
+
+ /**
* Protocol announcement for protocol version 1. This is the same as V0,
* except for this initial line.
*
@@ -329,6 +343,106 @@ public final class GitProtocolConstants {
*/
public static final String VERSION_2_REQUEST = "version=2"; //$NON-NLS-1$
+ /**
+ * The flush packet.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_FLUSH = "0000"; //$NON-NLS-1$
+
+ /**
+ * An alias for {@link #PACKET_FLUSH}. "Flush" is the name used in the C git
+ * documentation; the Java implementation calls this "end" in several
+ * places.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_END = PACKET_FLUSH;
+
+ /**
+ * The delimiter packet in protocol V2.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_DELIM = "0001"; //$NON-NLS-1$
+
+ /**
+ * A "deepen" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_DEEPEN = "deepen "; //$NON-NLS-1$
+
+ /**
+ * A "deepen-not" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_DEEPEN_NOT = "deepen-not "; //$NON-NLS-1$
+
+ /**
+ * A "deepen-since" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_DEEPEN_SINCE = "deepen-since "; //$NON-NLS-1$
+
+ /**
+ * An "ACK" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_ACK = "ACK "; //$NON-NLS-1$
+
+ /**
+ * A "done" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_DONE = "done"; //$NON-NLS-1$
+
+ /**
+ * A "ERR" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_ERR = "ERR "; //$NON-NLS-1$
+
+ /**
+ * A "have" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_HAVE = "have "; //$NON-NLS-1$
+
+ /**
+ * A "shallow" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_SHALLOW = OPTION_SHALLOW + ' ';
+
+ /**
+ * A "shallow" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_UNSHALLOW = "unshallow "; //$NON-NLS-1$
+
+ /**
+ * A "want" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_WANT = "want "; //$NON-NLS-1$
+
+ /**
+ * A "want-ref" packet beginning.
+ *
+ * @since 6.3
+ */
+ public static final String PACKET_WANT_REF = OPTION_WANT_REF + ' ';
+
enum MultiAck {
OFF, CONTINUE, DETAILED;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
index f8c51c180f..21a492577f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Google LLC. and others
+ * Copyright (C) 2018, 2022 Google LLC. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -10,6 +10,11 @@
package org.eclipse.jgit.transport;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT;
import java.io.EOFException;
import java.io.IOException;
@@ -70,20 +75,56 @@ final class ProtocolV0Parser {
break;
}
- if (line.startsWith("deepen ")) { //$NON-NLS-1$
- int depth = Integer.parseInt(line.substring(7));
+ if (line.startsWith(PACKET_DEEPEN)) {
+ int depth = Integer
+ .parseInt(line.substring(PACKET_DEEPEN.length()));
if (depth <= 0) {
throw new PackProtocolException(
MessageFormat.format(JGitText.get().invalidDepth,
Integer.valueOf(depth)));
}
+ if (reqBuilder.getDeepenSince() != 0) {
+ throw new PackProtocolException(
+ JGitText.get().deepenSinceWithDeepen);
+ }
+ if (reqBuilder.hasDeepenNots()) {
+ throw new PackProtocolException(
+ JGitText.get().deepenNotWithDeepen);
+ }
reqBuilder.setDepth(depth);
continue;
}
- if (line.startsWith("shallow ")) { //$NON-NLS-1$
+ if (line.startsWith(PACKET_DEEPEN_NOT)) {
+ reqBuilder.addDeepenNot(
+ line.substring(PACKET_DEEPEN_NOT.length()));
+ if (reqBuilder.getDepth() != 0) {
+ throw new PackProtocolException(
+ JGitText.get().deepenNotWithDeepen);
+ }
+ continue;
+ }
+
+ if (line.startsWith(PACKET_DEEPEN_SINCE)) {
+ // TODO: timestamps should be long
+ int ts = Integer
+ .parseInt(line.substring(PACKET_DEEPEN_SINCE.length()));
+ if (ts <= 0) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().invalidTimestamp, line));
+ }
+ if (reqBuilder.getDepth() != 0) {
+ throw new PackProtocolException(
+ JGitText.get().deepenSinceWithDeepen);
+ }
+ reqBuilder.setDeepenSince(ts);
+ continue;
+ }
+
+ if (line.startsWith(PACKET_SHALLOW)) {
reqBuilder.addClientShallowCommit(
- ObjectId.fromString(line.substring(8)));
+ ObjectId.fromString(
+ line.substring(PACKET_SHALLOW.length())));
continue;
}
@@ -101,7 +142,7 @@ final class ProtocolV0Parser {
continue;
}
- if (!line.startsWith("want ") || line.length() < 45) { //$NON-NLS-1$
+ if (!line.startsWith(PACKET_WANT) || line.length() < 45) {
throw new PackProtocolException(MessageFormat
.format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$
}
@@ -115,7 +156,8 @@ final class ProtocolV0Parser {
}
}
- reqBuilder.addWantId(ObjectId.fromString(line.substring(5)));
+ reqBuilder.addWantId(
+ ObjectId.fromString(line.substring(PACKET_WANT.length())));
isFirst = false;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
index 6cec4b9a3f..b38deb69c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, Google LLC. and others
+ * Copyright (C) 2018, 2022 Google LLC. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -20,7 +20,14 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_AL
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE;
-import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT_REF;
import java.io.IOException;
import java.text.MessageFormat;
@@ -115,15 +122,17 @@ final class ProtocolV2Parser {
boolean filterReceived = false;
for (String line2 : pckIn.readStrings()) {
- if (line2.startsWith("want ")) { //$NON-NLS-1$
- reqBuilder.addWantId(ObjectId.fromString(line2.substring(5)));
+ if (line2.startsWith(PACKET_WANT)) {
+ reqBuilder.addWantId(ObjectId
+ .fromString(line2.substring(PACKET_WANT.length())));
} else if (transferConfig.isAllowRefInWant()
- && line2.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$
+ && line2.startsWith(PACKET_WANT_REF)) {
reqBuilder.addWantedRef(
- line2.substring(OPTION_WANT_REF.length() + 1));
- } else if (line2.startsWith("have ")) { //$NON-NLS-1$
- reqBuilder.addPeerHas(ObjectId.fromString(line2.substring(5)));
- } else if (line2.equals("done")) { //$NON-NLS-1$
+ line2.substring(PACKET_WANT_REF.length()));
+ } else if (line2.startsWith(PACKET_HAVE)) {
+ reqBuilder.addPeerHas(ObjectId
+ .fromString(line2.substring(PACKET_HAVE.length())));
+ } else if (line2.equals(PACKET_DONE)) {
reqBuilder.setDoneReceived();
} else if (line2.equals(OPTION_WAIT_FOR_DONE)) {
reqBuilder.setWaitForDone();
@@ -135,11 +144,13 @@ final class ProtocolV2Parser {
reqBuilder.addClientCapability(OPTION_INCLUDE_TAG);
} else if (line2.equals(OPTION_OFS_DELTA)) {
reqBuilder.addClientCapability(OPTION_OFS_DELTA);
- } else if (line2.startsWith("shallow ")) { //$NON-NLS-1$
+ } else if (line2.startsWith(PACKET_SHALLOW)) {
reqBuilder.addClientShallowCommit(
- ObjectId.fromString(line2.substring(8)));
- } else if (line2.startsWith("deepen ")) { //$NON-NLS-1$
- int parsedDepth = Integer.parseInt(line2.substring(7));
+ ObjectId.fromString(
+ line2.substring(PACKET_SHALLOW.length())));
+ } else if (line2.startsWith(PACKET_DEEPEN)) {
+ int parsedDepth = Integer
+ .parseInt(line2.substring(PACKET_DEEPEN.length()));
if (parsedDepth <= 0) {
throw new PackProtocolException(
MessageFormat.format(JGitText.get().invalidDepth,
@@ -149,21 +160,23 @@ final class ProtocolV2Parser {
throw new PackProtocolException(
JGitText.get().deepenSinceWithDeepen);
}
- if (reqBuilder.hasDeepenNotRefs()) {
+ if (reqBuilder.hasDeepenNots()) {
throw new PackProtocolException(
JGitText.get().deepenNotWithDeepen);
}
reqBuilder.setDepth(parsedDepth);
- } else if (line2.startsWith("deepen-not ")) { //$NON-NLS-1$
- reqBuilder.addDeepenNotRef(line2.substring(11));
+ } else if (line2.startsWith(PACKET_DEEPEN_NOT)) {
+ reqBuilder.addDeepenNot(
+ line2.substring(PACKET_DEEPEN_NOT.length()));
if (reqBuilder.getDepth() != 0) {
throw new PackProtocolException(
JGitText.get().deepenNotWithDeepen);
}
} else if (line2.equals(OPTION_DEEPEN_RELATIVE)) {
reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE);
- } else if (line2.startsWith("deepen-since ")) { //$NON-NLS-1$
- int ts = Integer.parseInt(line2.substring(13));
+ } else if (line2.startsWith(PACKET_DEEPEN_SINCE)) {
+ int ts = Integer.parseInt(
+ line2.substring(PACKET_DEEPEN_SINCE.length()));
if (ts <= 0) {
throw new PackProtocolException(MessageFormat
.format(JGitText.get().invalidTimestamp, line2));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index 2542105c07..b70eedca63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -20,6 +20,8 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
@@ -1247,7 +1249,7 @@ public class ReceivePack {
public void sendAdvertisedRefs(RefAdvertiser adv)
throws IOException, ServiceMayNotContinueException {
if (advertiseError != null) {
- adv.writeOne("ERR " + advertiseError); //$NON-NLS-1$
+ adv.writeOne(PACKET_ERR + advertiseError);
return;
}
@@ -1255,7 +1257,7 @@ public class ReceivePack {
advertiseRefsHook.advertiseRefs(this);
} catch (ServiceMayNotContinueException fail) {
if (fail.getMessage() != null) {
- adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$
+ adv.writeOne(PACKET_ERR + fail.getMessage());
fail.setOutput();
}
throw fail;
@@ -1339,8 +1341,9 @@ public class ReceivePack {
break;
}
- if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$
- parseShallow(line.substring(8, 48));
+ int len = PACKET_SHALLOW.length() + 40;
+ if (line.length() >= len && line.startsWith(PACKET_SHALLOW)) {
+ parseShallow(line.substring(PACKET_SHALLOW.length(), len));
continue;
}
@@ -1795,9 +1798,9 @@ public class ReceivePack {
@Override
void sendString(String s) throws IOException {
if (reportStatus) {
- pckOut.writeString(s + "\n"); //$NON-NLS-1$
+ pckOut.writeString(s + '\n');
} else if (msgOut != null) {
- msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
+ msgOut.write(Constants.encode(s + '\n'));
}
}
};
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 e36eeccfb1..61d193593a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2013 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
@@ -154,7 +154,7 @@ public class RefSpec implements Serializable {
this.allowMismatchedWildcards = mode;
String s = spec;
- if (s.startsWith("^+") || s.startsWith("+^")) {
+ if (s.startsWith("^+") || s.startsWith("+^")) { //$NON-NLS-1$ //$NON-NLS-2$
throw new IllegalArgumentException(
JGitText.get().invalidNegativeAndForce);
}
@@ -164,7 +164,7 @@ public class RefSpec implements Serializable {
s = s.substring(1);
}
- if(s.startsWith("^")) {
+ if (s.startsWith("^")) { //$NON-NLS-1$
negative = true;
s = s.substring(1);
}
@@ -251,7 +251,7 @@ public class RefSpec implements Serializable {
* the specification is invalid.
*/
public RefSpec(String spec) {
- this(spec, spec.startsWith("^") ? WildcardMode.ALLOW_MISMATCH
+ this(spec, spec.startsWith("^") ? WildcardMode.ALLOW_MISMATCH //$NON-NLS-1$
: WildcardMode.REQUIRE_MATCH);
}
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 3222d6330c..7cea998474 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -27,6 +27,7 @@ import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -784,6 +785,12 @@ public abstract class Transport implements AutoCloseable {
private PrePushHook prePush;
+ private Integer depth;
+
+ private Instant deepenSince;
+
+ private List<String> deepenNots = new ArrayList<>();
+
@Nullable
TransferConfig.ProtocolVersion protocol;
@@ -1086,6 +1093,83 @@ public abstract class Transport implements AutoCloseable {
filterSpec = requireNonNull(filter);
}
+
+ /**
+ * Retrieves the depth for a shallow clone.
+ *
+ * @return the depth, or {@code null} if none set
+ * @since 6.3
+ */
+ public final Integer getDepth() {
+ return depth;
+ }
+
+ /**
+ * Limits fetching to the specified number of commits from the tip of each
+ * remote branch history.
+ *
+ * @param depth
+ * the depth
+ * @since 6.3
+ */
+ public final void setDepth(int depth) {
+ if (depth < 1) {
+ throw new IllegalArgumentException(JGitText.get().depthMustBeAt1);
+ }
+ this.depth = Integer.valueOf(depth);
+ }
+
+ /**
+ * Limits fetching to the specified number of commits from the tip of each
+ * remote branch history.
+ *
+ * @param depth
+ * the depth, or {@code null} to unset the depth
+ * @since 6.3
+ */
+ public final void setDepth(Integer depth) {
+ if (depth != null && depth.intValue() < 1) {
+ throw new IllegalArgumentException(JGitText.get().depthMustBeAt1);
+ }
+ this.depth = depth;
+ }
+
+ /**
+ * @return the deepen-since for a shallow clone
+ * @since 6.3
+ */
+ public final Instant getDeepenSince() {
+ return deepenSince;
+ }
+
+ /**
+ * Deepen or shorten the history of a shallow repository to include all reachable commits after a specified time.
+ *
+ * @param deepenSince the deepen-since. Must not be {@code null}
+ * @since 6.3
+ */
+ public final void setDeepenSince(@NonNull Instant deepenSince) {
+ this.deepenSince = deepenSince;
+ }
+
+ /**
+ * @return the deepen-not for a shallow clone
+ * @since 6.3
+ */
+ public final List<String> getDeepenNots() {
+ return deepenNots;
+ }
+
+ /**
+ * Deepen or shorten the history of a shallow repository to exclude commits reachable from a specified remote branch or tag.
+ *
+ * @param deepenNots the deepen-not. Must not be {@code null}
+ * @since 6.3
+ */
+ public final void setDeepenNots(@NonNull List<String> deepenNots) {
+ this.deepenNots = deepenNots;
+ }
+
/**
* Apply provided remote configuration on this transport.
*
@@ -1230,7 +1314,7 @@ public abstract class Transport implements AutoCloseable {
* @param toFetch
* specification of refs to fetch locally. May be null or the
* empty collection to use the specifications from the
- * RemoteConfig. May contains regular and negative
+ * RemoteConfig. May contains regular and negative
* {@link RefSpec}s. Source for each regular RefSpec can't
* be null.
* @return information describing the tracking refs updated.
@@ -1266,7 +1350,7 @@ public abstract class Transport implements AutoCloseable {
* @param toFetch
* specification of refs to fetch locally. May be null or the
* empty collection to use the specifications from the
- * RemoteConfig. May contains regular and negative
+ * RemoteConfig. May contain regular and negative
* {@link RefSpec}s. Source for each regular RefSpec can't
* be null.
* @param branch
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 1617c5063d..65dbf12b2f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2020 Google Inc. and others
+ * Copyright (C) 2008, 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
@@ -10,6 +10,7 @@
package org.eclipse.jgit.transport;
+import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -35,6 +36,12 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ACK;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_UNSHALLOW;
import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2_REQUEST;
import static org.eclipse.jgit.util.RefMap.toRefMap;
@@ -1033,6 +1040,7 @@ public class UploadPack implements Closeable {
// writing a response. Buffer the response until then.
PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator();
List<ObjectId> unshallowCommits = new ArrayList<>();
+ List<ObjectId> deepenNots = emptyList();
FetchRequest req;
try {
if (biDirectionalPipe)
@@ -1071,13 +1079,15 @@ public class UploadPack implements Closeable {
verifyClientShallow(req.getClientShallowCommits());
}
- if (req.getDepth() != 0 || req.getDeepenSince() != 0) {
+ deepenNots = parseDeepenNots(req.getDeepenNots());
+ if (req.getDepth() != 0 || req.getDeepenSince() != 0 || !req.getDeepenNots().isEmpty()) {
computeShallowsAndUnshallows(req, shallow -> {
- pckOut.writeString("shallow " + shallow.name() + '\n'); //$NON-NLS-1$
+ pckOut.writeString(PACKET_SHALLOW + shallow.name() + '\n');
}, unshallow -> {
- pckOut.writeString("unshallow " + unshallow.name() + '\n'); //$NON-NLS-1$
+ pckOut.writeString(
+ PACKET_UNSHALLOW + unshallow.name() + '\n');
unshallowCommits.add(unshallow);
- }, Collections.emptyList());
+ }, deepenNots);
pckOut.end();
}
@@ -1109,7 +1119,7 @@ public class UploadPack implements Closeable {
if (sendPack) {
sendPack(accumulator, req, refs == null ? null : refs.values(),
- unshallowCommits, Collections.emptyList(), pckOut);
+ unshallowCommits, deepenNots, pckOut);
}
}
@@ -1188,15 +1198,7 @@ public class UploadPack implements Closeable {
// TODO(ifrade): Refactor to pass around the Request object, instead of
// copying data back to class fields
- List<ObjectId> deepenNots = new ArrayList<>();
- for (String s : req.getDeepenNotRefs()) {
- Ref ref = findRef(s);
- if (ref == null) {
- throw new PackProtocolException(MessageFormat
- .format(JGitText.get().invalidRefName, s));
- }
- deepenNots.add(ref.getObjectId());
- }
+ List<ObjectId> deepenNots = parseDeepenNots(req.getDeepenNots());
Map<String, ObjectId> wantedRefs = wantedRefs(req);
// TODO(ifrade): Avoid mutating the parsed request.
@@ -1206,7 +1208,7 @@ public class UploadPack implements Closeable {
boolean sectionSent = false;
boolean mayHaveShallow = req.getDepth() != 0
|| req.getDeepenSince() != 0
- || !req.getDeepenNotRefs().isEmpty();
+ || !req.getDeepenNots().isEmpty();
List<ObjectId> shallowCommits = new ArrayList<>();
List<ObjectId> unshallowCommits = new ArrayList<>();
@@ -1232,7 +1234,7 @@ public class UploadPack implements Closeable {
GitProtocolConstants.SECTION_ACKNOWLEDGMENTS + '\n');
for (ObjectId id : req.getPeerHas()) {
if (walk.getObjectReader().has(id)) {
- pckOut.writeString("ACK " + id.getName() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ pckOut.writeString(PACKET_ACK + id.getName() + '\n');
}
}
processHaveLines(req.getPeerHas(), ObjectId.zeroId(),
@@ -1250,12 +1252,13 @@ public class UploadPack implements Closeable {
if (mayHaveShallow) {
if (sectionSent)
pckOut.writeDelim();
- pckOut.writeString("shallow-info\n"); //$NON-NLS-1$
+ pckOut.writeString(
+ GitProtocolConstants.SECTION_SHALLOW_INFO + '\n');
for (ObjectId o : shallowCommits) {
- pckOut.writeString("shallow " + o.getName() + '\n'); //$NON-NLS-1$
+ pckOut.writeString(PACKET_SHALLOW + o.getName() + '\n');
}
for (ObjectId o : unshallowCommits) {
- pckOut.writeString("unshallow " + o.getName() + '\n'); //$NON-NLS-1$
+ pckOut.writeString(PACKET_UNSHALLOW + o.getName() + '\n');
}
sectionSent = true;
}
@@ -1319,7 +1322,7 @@ public class UploadPack implements Closeable {
.format(JGitText.get().missingObject, oid.name()), e);
}
- pckOut.writeString(oid.getName() + " " + size); //$NON-NLS-1$
+ pckOut.writeString(oid.getName() + ' ' + size);
}
pckOut.end();
@@ -1391,7 +1394,7 @@ public class UploadPack implements Closeable {
protocolV2Hook
.onCapabilities(CapabilitiesV2Request.builder().build());
for (String s : getV2CapabilityAdvertisement()) {
- pckOut.writeString(s + "\n"); //$NON-NLS-1$
+ pckOut.writeString(s + '\n');
}
pckOut.end();
@@ -1618,7 +1621,7 @@ public class UploadPack implements Closeable {
*/
public void sendMessage(String what) {
try {
- msgOut.write(Constants.encode(what + "\n")); //$NON-NLS-1$
+ msgOut.write(Constants.encode(what + '\n'));
} catch (IOException e) {
// Ignore write failures.
}
@@ -1725,24 +1728,26 @@ public class UploadPack implements Closeable {
if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
pckOut.writeString("NAK\n"); //$NON-NLS-1$
if (noDone && sentReady) {
- pckOut.writeString("ACK " + last.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ pckOut.writeString(PACKET_ACK + last.name() + '\n');
return true;
}
if (!biDirectionalPipe)
return false;
pckOut.flush();
- } else if (line.startsWith("have ") && line.length() == 45) { //$NON-NLS-1$
- peerHas.add(ObjectId.fromString(line.substring(5)));
+ } else if (line.startsWith(PACKET_HAVE)
+ && line.length() == PACKET_HAVE.length() + 40) {
+ peerHas.add(ObjectId
+ .fromString(line.substring(PACKET_HAVE.length())));
accumulator.haves++;
- } else if (line.equals("done")) { //$NON-NLS-1$
+ } else if (line.equals(PACKET_DONE)) {
last = processHaveLines(peerHas, last, pckOut, accumulator, Option.NONE);
if (commonBase.isEmpty())
pckOut.writeString("NAK\n"); //$NON-NLS-1$
else if (multiAck != MultiAck.OFF)
- pckOut.writeString("ACK " + last.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ pckOut.writeString(PACKET_ACK + last.name() + '\n');
return true;
@@ -1803,14 +1808,15 @@ public class UploadPack implements Closeable {
//
switch (multiAck) {
case OFF:
- if (commonBase.size() == 1)
- out.writeString("ACK " + obj.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (commonBase.size() == 1) {
+ out.writeString(PACKET_ACK + obj.name() + '\n');
+ }
break;
case CONTINUE:
- out.writeString("ACK " + obj.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ out.writeString(PACKET_ACK + obj.name() + " continue\n"); //$NON-NLS-1$
break;
case DETAILED:
- out.writeString("ACK " + obj.name() + " common\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ out.writeString(PACKET_ACK + obj.name() + " common\n"); //$NON-NLS-1$
break;
}
}
@@ -1849,11 +1855,11 @@ public class UploadPack implements Closeable {
break;
case CONTINUE:
out.writeString(
- "ACK " + id.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ PACKET_ACK + id.name() + " continue\n"); //$NON-NLS-1$
break;
case DETAILED:
out.writeString(
- "ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ PACKET_ACK + id.name() + " ready\n"); //$NON-NLS-1$
readySent = true;
break;
}
@@ -1866,7 +1872,7 @@ public class UploadPack implements Closeable {
if (multiAck == MultiAck.DETAILED && !didOkToGiveUp
&& okToGiveUp()) {
ObjectId id = peerHas.get(peerHas.size() - 1);
- out.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ out.writeString(PACKET_ACK + id.name() + " ready\n"); //$NON-NLS-1$
readySent = true;
}
@@ -2038,7 +2044,8 @@ public class UploadPack implements Closeable {
.filter(obj -> !(obj instanceof RevCommit))
.limit(1)
.collect(Collectors.toList()).get(0);
- throw new WantNotValidException(nonCommit);
+ throw new WantNotValidException(nonCommit,
+ new Exception("Cannot walk without bitmaps")); //$NON-NLS-1$
}
try (ObjectWalk objWalk = walk.toObjectWalkWithSameObjects()) {
@@ -2052,6 +2059,11 @@ public class UploadPack implements Closeable {
Optional<RevObject> unreachable = reachabilityChecker
.areAllReachable(wantsAsObjs, startersAsObjs);
if (unreachable.isPresent()) {
+ if (!repoHasBitmaps) {
+ throw new WantNotValidException(
+ unreachable.get(), new Exception(
+ "Retry with bitmaps enabled")); //$NON-NLS-1$
+ }
throw new WantNotValidException(unreachable.get());
}
}
@@ -2470,6 +2482,24 @@ public class UploadPack implements Closeable {
}
}
+ private List<ObjectId> parseDeepenNots(List<String> deepenNots)
+ throws IOException {
+ List<ObjectId> result = new ArrayList<>();
+ for (String s : deepenNots) {
+ if (ObjectId.isId(s)) {
+ result.add(ObjectId.fromString(s));
+ } else {
+ Ref ref = findRef(s);
+ if (ref == null) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().invalidRefName, s));
+ }
+ result.add(ref.getObjectId());
+ }
+ }
+ return result;
+ }
+
private static class ResponseBufferedOutputStream extends OutputStream {
private final OutputStream rawOut;
@@ -2533,7 +2563,7 @@ public class UploadPack implements Closeable {
@Override
public void writeError(String message) throws IOException {
new PacketLineOut(requireNonNull(rawOut))
- .writeString("ERR " + message + '\n'); //$NON-NLS-1$
+ .writeString(PACKET_ERR + message + '\n');
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index 28b7423817..2fd945b03f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -56,7 +56,7 @@ import org.eclipse.jgit.lib.Repository;
public class NameConflictTreeWalk extends TreeWalk {
private static final int TREE_MODE = FileMode.TREE.getBits();
- private boolean fastMinHasMatch;
+ private boolean allTreesNamesMatchFastMinRef;
private AbstractTreeIterator dfConflict;
@@ -97,7 +97,7 @@ public class NameConflictTreeWalk extends TreeWalk {
AbstractTreeIterator min() throws CorruptObjectException {
for (;;) {
final AbstractTreeIterator minRef = fastMin();
- if (fastMinHasMatch)
+ if (allTreesNamesMatchFastMinRef)
return minRef;
if (isTree(minRef)) {
@@ -118,15 +118,14 @@ public class NameConflictTreeWalk extends TreeWalk {
}
private AbstractTreeIterator fastMin() {
- fastMinHasMatch = true;
-
- int i = 0;
+ int i = getFirstNonEofTreeIndex();
+ if (i == -1) {
+ // All trees reached the end.
+ allTreesNamesMatchFastMinRef = true;
+ return trees[trees.length - 1];
+ }
AbstractTreeIterator minRef = trees[i];
- while (minRef.eof() && ++i < trees.length)
- minRef = trees[i];
- if (minRef.eof())
- return minRef;
-
+ allTreesNamesMatchFastMinRef = true;
boolean hasConflict = false;
minRef.matches = minRef;
while (++i < trees.length) {
@@ -136,7 +135,7 @@ public class NameConflictTreeWalk extends TreeWalk {
final int cmp = t.pathCompare(minRef);
if (cmp < 0) {
- if (fastMinHasMatch && isTree(minRef) && !isTree(t)
+ if (allTreesNamesMatchFastMinRef && isTree(minRef) && !isTree(t)
&& nameEqual(minRef, t)) {
// We used to be at a tree, but now we are at a file
// with the same name. Allow the file to match the
@@ -145,7 +144,7 @@ public class NameConflictTreeWalk extends TreeWalk {
t.matches = minRef;
hasConflict = true;
} else {
- fastMinHasMatch = false;
+ allTreesNamesMatchFastMinRef = false;
t.matches = t;
minRef = t;
}
@@ -153,7 +152,7 @@ public class NameConflictTreeWalk extends TreeWalk {
// Exact name/mode match is best.
//
t.matches = minRef;
- } else if (fastMinHasMatch && isTree(t) && !isTree(minRef)
+ } else if (allTreesNamesMatchFastMinRef && isTree(t) && !isTree(minRef)
&& !isGitlink(minRef) && nameEqual(t, minRef)) {
// The minimum is a file (non-tree) but the next entry
// of this iterator is a tree whose name matches our file.
@@ -172,14 +171,23 @@ public class NameConflictTreeWalk extends TreeWalk {
minRef = t;
hasConflict = true;
} else
- fastMinHasMatch = false;
+ allTreesNamesMatchFastMinRef = false;
}
- if (hasConflict && fastMinHasMatch && dfConflict == null)
+ if (hasConflict && allTreesNamesMatchFastMinRef && dfConflict == null)
dfConflict = minRef;
return minRef;
}
+ private int getFirstNonEofTreeIndex() {
+ for (int i = 0; i < trees.length; i++) {
+ if (!trees[i].eof()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
private static boolean nameEqual(final AbstractTreeIterator a,
final AbstractTreeIterator b) {
return a.pathCompare(b, TREE_MODE) == 0;
@@ -268,6 +276,28 @@ public class NameConflictTreeWalk extends TreeWalk {
}
}
+ // When the combineDF is called, the t.matches field stores other
+ // entry (i.e. tree iterator) with an equal path. However, the
+ // previous loop moves each iterator independently. As a result,
+ // iterators which have had equals path at the start of the
+ // method can have different paths at this point.
+ // Reevaluate existing matches.
+ // The NameConflictTreeWalkTest.testDF_specialFileNames test
+ // cover this situation.
+ for (AbstractTreeIterator t : trees) {
+ // The previous loop doesn't touch tree iterator if it matches
+ // minRef. Skip it here
+ if (t.eof() || t.matches == null || t.matches == minRef)
+ continue;
+ // The t.pathCompare takes into account the entry type (file
+ // or directory) and returns non-zero value if names match
+ // but entry type don't match.
+ // We want to keep such matches (file/directory conflict),
+ // so reset matches only if names are not equal.
+ if (!nameEqual(t, t.matches))
+ t.matches = null;
+ }
+
if (treeMatch != null) {
// If we do have a conflict use one of the directory
// matching iterators instead of the file iterator.
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 8269666d26..ece945232e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -1375,6 +1375,14 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
System.arraycopy(tmp, 0, trees, 0, trees.length);
}
+ /**
+ * Returns an AbstractTreeIterator from {@code trees} with the smallest name, and sets its
+ * {@code matches} field. This may clobber {@code matches} in other {@code tree}s. Other iterators
+ * at the same name will have their {@code matches} pointing to the same {@code min()} value.
+ *
+ * @return the smallest tree iterator available.
+ * @throws CorruptObjectException
+ */
@SuppressWarnings("unused")
AbstractTreeIterator min() throws CorruptObjectException {
int i = 0;
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 427eac5b53..d8a61ec97a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -1004,6 +1004,12 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
return wtMode;
}
final FileMode iMode = indexIter.getEntryFileMode();
+ if (iMode == FileMode.SYMLINK
+ && getOptions().getSymLinks() == SymLinks.FALSE
+ && (wtMode == FileMode.REGULAR_FILE
+ || wtMode == FileMode.EXECUTABLE_FILE)) {
+ return iMode;
+ }
if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
return wtMode;
}
@@ -1274,11 +1280,15 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
IgnoreNode load() throws IOException {
- IgnoreNode r = new IgnoreNode();
+ return load(null);
+ }
+
+ IgnoreNode load(IgnoreNode parent) throws IOException {
+ IgnoreNodeWithParent r = new IgnoreNodeWithParent(parent);
try (InputStream in = entry.openInputStream()) {
r.parse(name, in);
}
- return r.getRules().isEmpty() ? null : r;
+ return r.getRules().isEmpty() && parent == null ? null : r;
}
}
@@ -1292,29 +1302,41 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
@Override
- IgnoreNode load() throws IOException {
- IgnoreNode r;
- if (entry != null) {
- r = super.load();
- if (r == null)
- r = new IgnoreNode();
- } else {
- r = new IgnoreNode();
- }
-
+ IgnoreNode load(IgnoreNode parent) throws IOException {
+ IgnoreNode coreExclude = new IgnoreNodeWithParent(parent);
FS fs = repository.getFS();
Path path = repository.getConfig().getPath(
ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null);
if (path != null) {
- loadRulesFromFile(r, path.toFile());
+ loadRulesFromFile(coreExclude, path.toFile());
+ }
+ if (coreExclude.getRules().isEmpty()) {
+ coreExclude = parent;
}
+ IgnoreNode infoExclude = new IgnoreNodeWithParent(
+ coreExclude);
File exclude = fs.resolve(repository.getDirectory(),
Constants.INFO_EXCLUDE);
- loadRulesFromFile(r, exclude);
+ loadRulesFromFile(infoExclude, exclude);
+ if (infoExclude.getRules().isEmpty()) {
+ infoExclude = null;
+ }
- return r.getRules().isEmpty() ? null : r;
+ IgnoreNode parentNode = infoExclude != null ? infoExclude
+ : coreExclude;
+
+ IgnoreNode r;
+ if (entry != null) {
+ r = super.load(parentNode);
+ if (r == null) {
+ return null;
+ }
+ } else {
+ return parentNode;
+ }
+ return r.getRules().isEmpty() ? parentNode : r;
}
private static void loadRulesFromFile(IgnoreNode r, File exclude)
@@ -1327,6 +1349,24 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
+ private static class IgnoreNodeWithParent extends IgnoreNode {
+
+ private final IgnoreNode parent;
+
+ IgnoreNodeWithParent(IgnoreNode parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public Boolean checkIgnored(String path, boolean isDirectory) {
+ Boolean result = super.checkIgnored(path, isDirectory);
+ if (result == null && parent != null) {
+ return parent.checkIgnored(path, isDirectory);
+ }
+ return result;
+ }
+ }
+
/** Magic type indicating we know rules exist, but they aren't loaded. */
private static class PerDirectoryAttributesNode extends AttributesNode {
final Entry entry;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
index 5a39f95b2b..ae13ef7f3c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
@@ -18,7 +18,8 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
*
* @since 4.2
*/
-public class Paths {
+public final class Paths {
+
/**
* Remove trailing {@code '/'} if present.
*
@@ -43,6 +44,33 @@ public class Paths {
}
/**
+ * Determines whether a git path {@code folder} is a prefix of another git
+ * path {@code path}, or the same as {@code path}. An empty {@code folder}
+ * is <em>not</em> not considered a prefix and matches only if {@code path}
+ * is also empty.
+ *
+ * @param folder
+ * a git path for a directory, without trailing slash
+ * @param path
+ * a git path
+ * @return {@code true} if {@code folder} is a directory prefix of
+ * {@code path}, or is equal to {@code path}, {@code false}
+ * otherwise
+ * @since 6.3
+ */
+ public static boolean isEqualOrPrefix(String folder, String path) {
+ if (folder.isEmpty()) {
+ return path.isEmpty();
+ }
+ boolean isPrefix = path.startsWith(folder);
+ if (isPrefix) {
+ int length = folder.length();
+ return path.length() == length || path.charAt(length) == '/';
+ }
+ return false;
+ }
+
+ /**
* Compare two paths according to Git path sort ordering rules.
*
* @param aPath
@@ -63,9 +91,8 @@ public class Paths {
* @param bMode
* mode of the second file. Trees are sorted as though
* {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
- * @return &lt;0 if {@code aPath} sorts before {@code bPath};
- * 0 if the paths are the same;
- * &gt;0 if {@code aPath} sorts after {@code bPath}.
+ * @return &lt;0 if {@code aPath} sorts before {@code bPath}; 0 if the paths
+ * are the same; &gt;0 if {@code aPath} sorts after {@code bPath}.
*/
public static int compare(byte[] aPath, int aPos, int aEnd, int aMode,
byte[] bPath, int bPos, int bEnd, int bMode) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index 16e2577911..5ced0713e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -63,6 +63,8 @@ public abstract class SystemReader {
private static volatile Boolean isWindows;
+ private static volatile Boolean isLinux;
+
static {
SystemReader r = new Default();
r.init();
@@ -185,6 +187,7 @@ public abstract class SystemReader {
public static void setInstance(SystemReader newReader) {
isMacOS = null;
isWindows = null;
+ isLinux = null;
if (newReader == null)
INSTANCE = DEFAULT;
else {
@@ -543,6 +546,20 @@ public abstract class SystemReader {
return isMacOS.booleanValue();
}
+ /**
+ * Whether we are running on Linux.
+ *
+ * @return true if we are running on Linux.
+ * @since 6.3
+ */
+ public boolean isLinux() {
+ if (isLinux == null) {
+ String osname = getOsName();
+ isLinux = Boolean.valueOf(osname.toLowerCase().startsWith("linux")); //$NON-NLS-1$
+ }
+ return isLinux.booleanValue();
+ }
+
private String getOsName() {
return AccessController.doPrivileged(
(PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$