summaryrefslogtreecommitdiffstats
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/.settings/org.eclipse.jdt.core.prefs2
-rw-r--r--org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs2
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF104
-rw-r--r--org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF4
-rw-r--r--org.eclipse.jgit/pom.xml2
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java29
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java137
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java56
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java146
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java924
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java44
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java44
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java25
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java25
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java78
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java25
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java125
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java42
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java194
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java147
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java247
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java246
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java169
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java832
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java158
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java48
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java202
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java222
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java488
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java89
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java25
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java83
112 files changed, 4273 insertions, 1731 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index c62afb31ed..857fcac17d 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -34,12 +34,29 @@
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
- <filter id="337768515">
+ <resource path="src/org/eclipse/jgit/gitrepo/RepoCommand.java" type="org.eclipse.jgit.gitrepo.RepoCommand$DefaultRemoteReader">
+ <filter id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.gitrepo.RepoCommand.DefaultRemoteReader"/>
+ <message_argument value="readFile(String, String, String)"/>
+ </message_arguments>
+ </filter>
+ <filter id="338792546">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.gitrepo.RepoCommand.DefaultRemoteReader"/>
+ <message_argument value="readFileFromRepo(Repository, String, String)"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/gitrepo/RepoCommand.java" type="org.eclipse.jgit.gitrepo.RepoCommand$RemoteReader">
+ <filter id="403804204">
<message_arguments>
- <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+ <message_argument value="org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader"/>
+ <message_argument value="readFileWithMode(String, String, String)"/>
</message_arguments>
</filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
@@ -75,6 +92,26 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/revwalk/DepthWalk.java" type="org.eclipse.jgit.revwalk.DepthWalk">
+ <filter id="403804204">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.revwalk.DepthWalk"/>
+ <message_argument value="getDeepenNotFlag()"/>
+ </message_arguments>
+ </filter>
+ <filter id="404000815">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.revwalk.DepthWalk"/>
+ <message_argument value="getDeepenNots()"/>
+ </message_arguments>
+ </filter>
+ <filter id="404000815">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.revwalk.DepthWalk"/>
+ <message_argument value="getDeepenSince()"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
<filter id="336658481">
<message_arguments>
@@ -131,10 +168,11 @@
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/transport/GitProtocolConstants.java" type="org.eclipse.jgit.transport.GitProtocolConstants">
- <filter id="337768515">
+ <resource path="src/org/eclipse/jgit/transport/RemoteSession.java" type="org.eclipse.jgit.transport.RemoteSession">
+ <filter id="404000815">
<message_arguments>
- <message_argument value="org.eclipse.jgit.transport.GitProtocolConstants"/>
+ <message_argument value="org.eclipse.jgit.transport.RemoteSession"/>
+ <message_argument value="getFtpChannel()"/>
</message_arguments>
</filter>
</resource>
@@ -152,6 +190,14 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/transport/http/HttpConnection.java" type="org.eclipse.jgit.transport.http.HttpConnection">
+ <filter id="403804204">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.transport.http.HttpConnection"/>
+ <message_argument value="getHeaderFields(String)"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
<filter id="1142947843">
<message_arguments>
@@ -175,13 +221,6 @@
</filter>
</resource>
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
- <filter id="1141899266">
- <message_arguments>
- <message_argument value="4.7"/>
- <message_argument value="5.1"/>
- <message_argument value="createNewFileAtomic(File)"/>
- </message_arguments>
- </filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="4.5.6"/>
@@ -235,15 +274,6 @@
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$LockToken">
- <filter id="1141899266">
- <message_arguments>
- <message_argument value="4.7"/>
- <message_argument value="5.1"/>
- <message_argument value="LockToken"/>
- </message_arguments>
- </filter>
- </resource>
<resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
<filter id="1142947843">
<message_arguments>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index 13c32a6d94..ef6f5e732f 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -91,7 +91,7 @@ org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
diff --git a/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs
index 0cba949fb7..2fca432276 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.mylyn.team.ui.prefs
@@ -1,3 +1,3 @@
#Tue Jul 19 20:11:28 CEST 2011
-commit.comment.template=${task.description} \n\nBug\: ${task.key}
+commit.comment.template=${task.description}\n\nBug\: ${task.key}
eclipse.preferences.version=1
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index ba51c4b087..66701b0b38 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: %plugin_name
Automatic-Module-Name: org.eclipse.jgit
Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 5.1.12.qualifier
+Bundle-Version: 5.2.3.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="5.1.12",
- org.eclipse.jgit.api;version="5.1.12";
+Export-Package: org.eclipse.jgit.annotations;version="5.2.3",
+ org.eclipse.jgit.api;version="5.2.3";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff,
@@ -22,65 +22,73 @@ Export-Package: org.eclipse.jgit.annotations;version="5.1.12",
org.eclipse.jgit.submodule,
org.eclipse.jgit.transport,
org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="5.1.12";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="5.1.12",
- org.eclipse.jgit.blame;version="5.1.12";
+ org.eclipse.jgit.api.errors;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="5.2.3",
+ org.eclipse.jgit.blame;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="5.1.12";
+ org.eclipse.jgit.diff;version="5.2.3";
uses:="org.eclipse.jgit.patch,
org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="5.1.12";
+ org.eclipse.jgit.dircache;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util,
org.eclipse.jgit.events,
org.eclipse.jgit.attributes",
- org.eclipse.jgit.errors;version="5.1.12";
+ org.eclipse.jgit.errors;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.internal.storage.pack,
org.eclipse.jgit.transport,
org.eclipse.jgit.dircache",
- org.eclipse.jgit.events;version="5.1.12";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="5.1.12",
- org.eclipse.jgit.gitrepo;version="5.1.12";
+ org.eclipse.jgit.events;version="5.2.3";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="5.2.3",
+ org.eclipse.jgit.gitrepo;version="5.2.3";
uses:="org.eclipse.jgit.api,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.xml.sax.helpers,
org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="5.1.12";x-internal:=true,
- org.eclipse.jgit.hooks;version="5.1.12";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="5.1.12",
- org.eclipse.jgit.ignore.internal;version="5.1.12";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="5.1.12";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="5.1.12";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.ketch;version="5.1.12";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="5.1.12";
+ org.eclipse.jgit.gitrepo.internal;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.hooks;version="5.2.3";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="5.2.3",
+ org.eclipse.jgit.ignore.internal;version="5.2.3";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="5.2.3";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.fsck;version="5.2.3";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.ketch;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.revwalk;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.internal.storage.dfs;version="5.2.3";
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="5.1.12";
+ org.eclipse.jgit.internal.storage.file;version="5.2.3";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.junit.http,
org.eclipse.jgit.http.server,
org.eclipse.jgit.lfs,
org.eclipse.jgit.pgm,
- org.eclipse.jgit.pgm.test",
- org.eclipse.jgit.internal.storage.io;version="5.1.12";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="5.1.12";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="5.1.12";
- x-friends:="org.eclipse.jgit.http.test,org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftree;version="5.1.12";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="5.1.12";
+ org.eclipse.jgit.pgm.test,
+ org.eclipse.jgit.ssh.apache",
+ org.eclipse.jgit.internal.storage.io;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.pack;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftable;version="5.2.3";
+ x-friends:="org.eclipse.jgit.http.test,
+ org.eclipse.jgit.junit,
+ org.eclipse.jgit.test,
+ org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftree;version="5.2.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.submodule;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.internal.transport.parser;version="5.2.3";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.server",
+ org.eclipse.jgit.internal.transport.ssh;version="5.2.3";x-friends:="org.eclipse.jgit.ssh.apache",
+ org.eclipse.jgit.lib;version="5.2.3";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util,
@@ -90,33 +98,33 @@ Export-Package: org.eclipse.jgit.annotations;version="5.1.12",
org.eclipse.jgit.treewalk,
org.eclipse.jgit.transport,
org.eclipse.jgit.submodule",
- org.eclipse.jgit.lib.internal;version="5.1.12";x-internal:=true,
- org.eclipse.jgit.merge;version="5.1.12";
+ org.eclipse.jgit.lib.internal;version="5.2.3";x-internal:=true,
+ org.eclipse.jgit.merge;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.diff,
org.eclipse.jgit.dircache,
org.eclipse.jgit.api",
- org.eclipse.jgit.nls;version="5.1.12",
- org.eclipse.jgit.notes;version="5.1.12";
+ org.eclipse.jgit.nls;version="5.2.3",
+ org.eclipse.jgit.notes;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="5.1.12";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="5.1.12";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="5.1.12";
+ org.eclipse.jgit.patch;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff,
org.eclipse.jgit.revwalk.filter",
- org.eclipse.jgit.revwalk.filter;version="5.1.12";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="5.1.12";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="5.1.12";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="5.1.12";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="5.1.12";
+ org.eclipse.jgit.revwalk.filter;version="5.2.3";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="5.2.3";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="5.2.3";
uses:="org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.internal.storage.pack,
@@ -128,24 +136,24 @@ Export-Package: org.eclipse.jgit.annotations;version="5.1.12",
org.eclipse.jgit.transport.http,
org.eclipse.jgit.errors,
org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="5.1.12";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="5.1.12";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="5.1.12";
+ org.eclipse.jgit.transport.http;version="5.2.3";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="5.2.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.attributes,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util,
org.eclipse.jgit.dircache",
- org.eclipse.jgit.treewalk.filter;version="5.1.12";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="5.1.12";
+ org.eclipse.jgit.treewalk.filter;version="5.2.3";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="5.2.3";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.transport.http,
org.eclipse.jgit.storage.file,
org.ietf.jgss",
- org.eclipse.jgit.util.io;version="5.1.12",
- org.eclipse.jgit.util.sha1;version="5.1.12",
- org.eclipse.jgit.util.time;version="5.1.12"
+ org.eclipse.jgit.util.io;version="5.2.3",
+ org.eclipse.jgit.util.sha1;version="5.2.3",
+ org.eclipse.jgit.util.time;version="5.2.3"
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
com.jcraft.jsch;version="[0.1.37,0.2.0)",
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 5b0bbcf766..8e22086cce 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: 5.1.12.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="5.1.12.qualifier";roots="."
+Bundle-Version: 5.2.3.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="5.2.3.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index e8704872ab..c30d648c69 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -53,7 +53,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>5.1.12-SNAPSHOT</version>
+ <version>5.2.3-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 dc1db81402..9ab03c251c 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -473,6 +473,7 @@ newIdMustNotBeNull=New ID must not be null
newlineInQuotesNotAllowed=Newline in quotes not allowed
noApplyInDelete=No apply in delete
noClosingBracket=No closing {0} found for {1} at index {2}.
+noCommitsSelectedForShallow=No commits selected for shallow request
noCredentialsProvider=Authentication is required but no CredentialsProvider has been registered
noHEADExistsAndNoExplicitStartingRevisionWasSpecified=No HEAD exists and no explicit starting revision was specified
noHMACsupport=No {0} support: {1}
@@ -647,8 +648,8 @@ sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0}
squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD
sshCommandFailed=Execution of ssh command ''{0}'' failed with error ''{1}''
sshUserNameError=Jsch error: failed to set SSH user name correctly to ''{0}''; using ''{1}'' picked up from SSH config file.
-sslFailureExceptionMessage=Secure connection to {0} could not be stablished because of SSL problems
-sslFailureInfo=A secure connection to {0}\ncould not be established because the server''s certificate could not be validated.
+sslFailureExceptionMessage=Secure connection to {0} could not be established because of SSL problems
+sslFailureInfo=A secure connection to {0} could not be established because the server''s certificate could not be validated.
sslFailureCause=SSL reported: {0}
sslFailureTrustExplanation=Do you want to skip SSL verification for this server?
sslTrustAlways=Always skip SSL verification for this server from now on
@@ -680,7 +681,6 @@ submodulePathInvalid=Invalid submodule path ''{0}''
submoduleUrlInvalid=Invalid submodule URL ''{0}''
submodulesNotSupported=Submodules are not supported
supportOnlyPackIndexVersion2=Only support index version 2
-symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java.
systemConfigFileInvalid=System wide config file {0} is invalid {1}
tagAlreadyExists=tag ''{0}'' already exists
tagNameInvalid=tag name {0} is invalid
@@ -781,7 +781,9 @@ uriNotFound={0} not found
uriNotFoundWithMessage={0} not found: {1}
URINotSupported=URI not supported: {0}
userConfigInvalid=Git config in the user's home directory {0} is invalid {1}
+validatingGitModules=Validating .gitmodules files
walkFailure=Walk failure.
+wantNoSpaceWithCapabilities=No space between oid and first capability in first want line
wantNotValid=want {0} not valid
weeksAgo={0} weeks ago
windowSizeMustBeLesserThanLimit=Window size must be < limit
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 5b84032b15..c6f3c671a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -42,11 +42,14 @@
*/
package org.eclipse.jgit.api;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.io.File;
import java.io.FileOutputStream;
-import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -258,7 +261,8 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
- try (FileWriter fw = new FileWriter(f)) {
+ try (Writer fw = new OutputStreamWriter(new FileOutputStream(f),
+ UTF_8)) {
fw.write(sb.toString());
}
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 11edb10894..455a2e665f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -269,7 +269,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
try {
dco = new DirCacheCheckout(repo, headTree, dc,
newCommit.getTree());
- dco.setFailOnConflict(true);
+ dco.setFailOnConflict(!force);
dco.setProgressMonitor(monitor);
try {
dco.checkout();
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 eee3da6f63..10a54ee459 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -283,12 +283,11 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
config.addURI(u);
final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES
- + config.getName() + "/") + "*"; //$NON-NLS-1$//$NON-NLS-2$
- RefSpec refSpec = new RefSpec();
- refSpec = refSpec.setForceUpdate(true);
- refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", dst); //$NON-NLS-1$
+ + config.getName() + '/') + '*';
+ boolean fetchAll = cloneAllBranches || branchesToClone == null
+ || branchesToClone.isEmpty();
- config.addFetchRefSpec(refSpec);
+ config.setFetchRefSpecs(calculateRefSpecs(fetchAll, dst));
config.update(clonedRepo.getConfig());
clonedRepo.getConfig().save();
@@ -297,27 +296,25 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
FetchCommand command = new FetchCommand(clonedRepo);
command.setRemote(remote);
command.setProgressMonitor(monitor);
- command.setTagOpt(TagOpt.FETCH_TAGS);
+ command.setTagOpt(fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW);
configure(command);
- List<RefSpec> specs = calculateRefSpecs(dst);
- command.setRefSpecs(specs);
-
return command.call();
}
- private List<RefSpec> calculateRefSpecs(String dst) {
+ private List<RefSpec> calculateRefSpecs(boolean fetchAll, String dst) {
RefSpec wcrs = new RefSpec();
wcrs = wcrs.setForceUpdate(true);
- wcrs = wcrs.setSourceDestination(Constants.R_HEADS + "*", dst); //$NON-NLS-1$
+ wcrs = wcrs.setSourceDestination(Constants.R_HEADS + '*', dst);
List<RefSpec> specs = new ArrayList<>();
- if (cloneAllBranches)
- specs.add(wcrs);
- else if (branchesToClone != null
- && branchesToClone.size() > 0) {
- for (String selectedRef : branchesToClone)
- if (wcrs.matchSource(selectedRef))
+ if (!fetchAll) {
+ for (String selectedRef : branchesToClone) {
+ if (wcrs.matchSource(selectedRef)) {
specs.add(wcrs.expandFromSource(selectedRef));
+ }
+ }
+ } else {
+ specs.add(wcrs);
}
return specs;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
index 28a27a90e0..29a51a0f02 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
@@ -44,6 +44,10 @@
*/
package org.eclipse.jgit.api;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -56,7 +60,6 @@ import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -113,17 +116,18 @@ public class ListBranchCommand extends GitCommand<List<Ref>> {
Collection<Ref> refs = new ArrayList<>();
// Also return HEAD if it's detached
- Ref head = repo.exactRef(Constants.HEAD);
- if (head != null && head.getLeaf().getName().equals(Constants.HEAD))
+ Ref head = repo.exactRef(HEAD);
+ if (head != null && head.getLeaf().getName().equals(HEAD)) {
refs.add(head);
+ }
if (listMode == null) {
- refs.addAll(getRefs(Constants.R_HEADS));
+ refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_HEADS));
} else if (listMode == ListMode.REMOTE) {
- refs.addAll(getRefs(Constants.R_REMOTES));
+ refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_REMOTES));
} else {
- refs.addAll(getRefs(Constants.R_HEADS));
- refs.addAll(getRefs(Constants.R_REMOTES));
+ refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_HEADS,
+ R_REMOTES));
}
resultRefs = new ArrayList<>(filterRefs(refs));
} catch (IOException e) {
@@ -185,8 +189,4 @@ public class ListBranchCommand extends GitCommand<List<Ref>> {
this.containsCommitish = containsCommitish;
return this;
}
-
- private Collection<Ref> getRefs(String prefix) throws IOException {
- return repo.getRefDatabase().getRefsByPrefix(prefix);
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
index 244a15686f..f92455a96a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -179,21 +179,6 @@ public class SubmoduleAddCommand extends
// Use the path as the default.
name = path;
}
- if (name.contains("/../") || name.contains("\\..\\") //$NON-NLS-1$ //$NON-NLS-2$
- || name.startsWith("../") || name.startsWith("..\\") //$NON-NLS-1$ //$NON-NLS-2$
- || name.endsWith("/..") || name.endsWith("\\..")) { //$NON-NLS-1$ //$NON-NLS-2$
- // Submodule names are used to store the submodule repositories
- // under $GIT_DIR/modules. Having ".." in submodule names makes a
- // vulnerability (CVE-2018-11235
- // https://bugs.eclipse.org/bugs/show_bug.cgi?id=535027#c0)
- // Reject the names with them. The callers need to make sure the
- // names free from these. We don't automatically replace these
- // characters or canonicalize by regarding the name as a file path.
- // Since Path class is platform dependent, we manually check '/' and
- // '\\' patterns here.
- throw new IllegalArgumentException(MessageFormat
- .format(JGitText.get().invalidNameContainsDotDot, name));
- }
try {
SubmoduleValidator.assertValidSubmoduleName(name);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java
index f60926c562..d73453c5af 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportConfigCallback.java
@@ -68,5 +68,5 @@ public interface TransportConfigCallback {
* @param transport
* a {@link org.eclipse.jgit.transport.Transport} object.
*/
- public void configure(Transport transport);
+ void configure(Transport transport);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
index f1d7d7be0e..2d1cde12e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
@@ -63,7 +63,7 @@ public interface AttributesNodeProvider {
* @throws java.io.IOException
* if an error is raised while parsing the attributes file
*/
- public AttributesNode getInfoAttributesNode() throws IOException;
+ AttributesNode getInfoAttributesNode() throws IOException;
/**
* Retrieve the {@link org.eclipse.jgit.attributes.AttributesNode} that
@@ -76,6 +76,6 @@ public interface AttributesNodeProvider {
* attributes file
* @see CoreConfig#getAttributesFile()
*/
- public AttributesNode getGlobalAttributesNode() throws IOException;
+ AttributesNode getGlobalAttributesNode() throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
index 1545e3523d..7b51f6dd32 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
@@ -53,5 +53,5 @@ public interface AttributesProvider {
*
* @return the currently active attributes
*/
- public Attributes getAttributes();
+ Attributes getAttributes();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java
index c4357d1297..0bb4516297 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommand.java
@@ -95,7 +95,7 @@ public abstract class FilterCommand {
* -1. -1 means that the {@link java.io.InputStream} is completely
* processed.
* @throws java.io.IOException
- * when {@link java.io.IOException} occured while reading from
+ * when {@link java.io.IOException} occurred while reading from
* {@link #in} or writing to {@link #out}
*/
public abstract int run() throws IOException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java
index 11b76b0d90..78573d2a63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandFactory.java
@@ -69,7 +69,7 @@ public interface FilterCommandFactory {
* thrown when the command constructor throws an
* java.io.IOException
*/
- public FilterCommand create(Repository db, InputStream in,
- OutputStream out) throws IOException;
+ FilterCommand create(Repository db, InputStream in, OutputStream out)
+ throws IOException;
}
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 5110d77dc9..8aa97df777 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -525,7 +525,7 @@ public class DirCacheCheckout {
builder.finish();
// init progress reporting
- int numTotal = removed.size() + updated.size();
+ int numTotal = removed.size() + updated.size() + conflicts.size();
monitor.beginTask(JGitText.get().checkingOutFiles, numTotal);
performingCheckout = true;
@@ -600,6 +600,33 @@ public class DirCacheCheckout {
}
throw ex;
}
+ for (String conflict : conflicts) {
+ // the conflicts are likely to have multiple entries in the
+ // dircache, we only want to check out the one for the "theirs"
+ // tree
+ int entryIdx = dc.findEntry(conflict);
+ if (entryIdx >= 0) {
+ while (entryIdx < dc.getEntryCount()) {
+ DirCacheEntry entry = dc.getEntry(entryIdx);
+ if (!entry.getPathString().equals(conflict)) {
+ break;
+ }
+ if (entry.getStage() == DirCacheEntry.STAGE_3) {
+ checkoutEntry(repo, entry, objectReader, false,
+ null);
+ break;
+ }
+ ++entryIdx;
+ }
+ }
+
+ monitor.update(1);
+ if (monitor.isCancelled()) {
+ throw new CanceledException(MessageFormat.format(
+ JGitText.get().operationCanceled,
+ JGitText.get().checkingOutFiles));
+ }
+ }
monitor.endTask();
// commit the index builder - a new index is persisted
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
index 19c916f810..6196e758a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
@@ -44,6 +44,8 @@
package org.eclipse.jgit.dircache;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@@ -75,7 +77,7 @@ import org.eclipse.jgit.util.RawParseUtils;
public class DirCacheIterator extends AbstractTreeIterator {
/** Byte array holding ".gitattributes" string */
private static final byte[] DOT_GIT_ATTRIBUTES_BYTES = Constants.DOT_GIT_ATTRIBUTES
- .getBytes();
+ .getBytes(UTF_8);
/** The cache this iterator was created to walk. */
protected final DirCache cache;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
index 9fbcc4dd50..afd7889f8a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
@@ -94,7 +94,8 @@ public class WorkingTreeModifiedEvent
*
* @return the set
*/
- public @NonNull Collection<String> getModified() {
+ @NonNull
+ public Collection<String> getModified() {
Collection<String> result = modified;
if (result == null) {
result = Collections.emptyList();
@@ -109,7 +110,8 @@ public class WorkingTreeModifiedEvent
*
* @return the set
*/
- public @NonNull Collection<String> getDeleted() {
+ @NonNull
+ public Collection<String> getDeleted() {
Collection<String> result = deleted;
if (result == null) {
result = Collections.emptyList();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
index 49839f8e6e..e8f6844dda 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java
@@ -54,5 +54,5 @@ interface Head {
* the character which decides which heads are returned.
* @return a list of heads based on the input.
*/
- public abstract List<Head> getNextHeads(char c);
+ List<Head> getNextHeads(char c);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index 26e783ddd7..8e463415b8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -358,7 +358,8 @@ public class ManifestParser extends DefaultHandler {
*
* @return filtered projects list reference, never null
*/
- public @NonNull List<RepoProject> getFilteredProjects() {
+ @NonNull
+ public List<RepoProject> getFilteredProjects() {
return filteredProjects;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 5a73cdc067..e9d86dfa83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -59,12 +59,14 @@ import java.util.Objects;
import java.util.StringJoiner;
import java.util.TreeMap;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.GitCommand;
import org.eclipse.jgit.api.SubmoduleAddCommand;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -80,7 +82,6 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
@@ -90,6 +91,7 @@ import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FileUtils;
/**
@@ -144,7 +146,9 @@ public class RepoCommand extends GitCommand<RevCommit> {
* @param uri
* The URI of the remote repository
* @param ref
- * The ref (branch/tag/etc.) to read
+ * Name of the ref to lookup. May be a short-hand form, e.g.
+ * "master" which is is automatically expanded to
+ * "refs/heads/master" if "refs/heads/master" already exists.
* @return the sha1 of the remote repository, or null if the ref does
* not exist.
* @throws GitAPIException
@@ -165,13 +169,93 @@ public class RepoCommand extends GitCommand<RevCommit> {
* @throws GitAPIException
* @throws IOException
* @since 3.5
+ *
+ * @deprecated Use {@link #readFileWithMode(String, String, String)}
+ * instead
+ */
+ @Deprecated
+ public default byte[] readFile(String uri, String ref, String path)
+ throws GitAPIException, IOException {
+ return readFileWithMode(uri, ref, path).getContents();
+ }
+
+ /**
+ * Read contents and mode (i.e. permissions) of the file from a remote
+ * repository.
+ *
+ * @param uri
+ * The URI of the remote repository
+ * @param ref
+ * Name of the ref to lookup. May be a short-hand form, e.g.
+ * "master" which is is automatically expanded to
+ * "refs/heads/master" if "refs/heads/master" already exists.
+ * @param path
+ * The relative path (inside the repo) to the file to read
+ * @return The contents and file mode of the file in the given
+ * repository and branch. Never null.
+ * @throws GitAPIException
+ * If the ref have an invalid or ambiguous name, or it does
+ * not exist in the repository,
+ * @throws IOException
+ * If the object does not exist or is too large
+ * @since 5.2
*/
- public byte[] readFile(String uri, String ref, String path)
+ @NonNull
+ public RemoteFile readFileWithMode(String uri, String ref, String path)
throws GitAPIException, IOException;
}
+ /**
+ * Read-only view of contents and file mode (i.e. permissions) for a file in
+ * a remote repository.
+ *
+ * @since 5.2
+ */
+ public static final class RemoteFile {
+ @NonNull
+ private final byte[] contents;
+
+ @NonNull
+ private final FileMode fileMode;
+
+ /**
+ * @param contents
+ * Raw contents of the file.
+ * @param fileMode
+ * Git file mode for this file (e.g. executable or regular)
+ */
+ public RemoteFile(@NonNull byte[] contents,
+ @NonNull FileMode fileMode) {
+ this.contents = Objects.requireNonNull(contents);
+ this.fileMode = Objects.requireNonNull(fileMode);
+ }
+
+ /**
+ * Contents of the file.
+ * <p>
+ * Callers who receive this reference must not modify its contents (as
+ * it can point to internal cached data).
+ *
+ * @return Raw contents of the file. Do not modify it.
+ */
+ @NonNull
+ public byte[] getContents() {
+ return contents;
+ }
+
+ /**
+ * @return Git file mode for this file (e.g. executable or regular)
+ */
+ @NonNull
+ public FileMode getFileMode() {
+ return fileMode;
+ }
+
+ }
+
/** A default implementation of {@link RemoteReader} callback. */
public static class DefaultRemoteReader implements RemoteReader {
+
@Override
public ObjectId sha1(String uri, String ref) throws GitAPIException {
Map<String, Ref> map = Git
@@ -183,38 +267,30 @@ public class RepoCommand extends GitCommand<RevCommit> {
}
@Override
- public byte[] readFile(String uri, String ref, String path)
+ public RemoteFile readFileWithMode(String uri, String ref, String path)
throws GitAPIException, IOException {
File dir = FileUtils.createTempDir("jgit_", ".git", null); //$NON-NLS-1$ //$NON-NLS-2$
try (Git git = Git.cloneRepository().setBare(true).setDirectory(dir)
.setURI(uri).call()) {
- return readFileFromRepo(git.getRepository(), ref, path);
+ Repository repo = git.getRepository();
+ ObjectId refCommitId = sha1(uri, ref);
+ if (refCommitId == null) {
+ throw new InvalidRefNameException(MessageFormat
+ .format(JGitText.get().refNotResolved, ref));
+ }
+ RevCommit commit = repo.parseCommit(refCommitId);
+ TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
+
+ // TODO(ifrade): Cope better with big files (e.g. using
+ // InputStream instead of byte[])
+ return new RemoteFile(
+ tw.getObjectReader().open(tw.getObjectId(0))
+ .getCachedBytes(Integer.MAX_VALUE),
+ tw.getFileMode(0));
} finally {
FileUtils.delete(dir, FileUtils.RECURSIVE);
}
}
-
- /**
- * Read a file from the repository
- *
- * @param repo
- * The repository containing the file
- * @param ref
- * The ref (branch/tag/etc.) to read
- * @param path
- * The relative path (inside the repo) to the file to read
- * @return the file's content
- * @throws GitAPIException
- * @throws IOException
- * @since 3.5
- */
- protected byte[] readFileFromRepo(Repository repo,
- String ref, String path) throws GitAPIException, IOException {
- try (ObjectReader reader = repo.newObjectReader()) {
- ObjectId oid = repo.resolve(ref + ":" + path); //$NON-NLS-1$
- return reader.open(oid).getBytes(Integer.MAX_VALUE);
- }
- }
}
@SuppressWarnings("serial")
@@ -587,12 +663,13 @@ public class RepoCommand extends GitCommand<RevCommit> {
builder.add(dcEntry);
for (CopyFile copyfile : proj.getCopyFiles()) {
- byte[] src = callback.readFile(
+ RemoteFile rf = callback.readFileWithMode(
url, proj.getRevision(), copyfile.src);
- objectId = inserter.insert(Constants.OBJ_BLOB, src);
+ objectId = inserter.insert(Constants.OBJ_BLOB,
+ rf.getContents());
dcEntry = new DirCacheEntry(copyfile.dest);
dcEntry.setObjectId(objectId);
- dcEntry.setFileMode(FileMode.REGULAR_FILE);
+ dcEntry.setFileMode(rf.getFileMode());
builder.add(dcEntry);
}
for (LinkFile linkfile : proj.getLinkFiles()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
index 7ba83c7cbf..d79dfa8b2f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -136,6 +136,7 @@ public class RepoProject implements Comparable<RepoProject> {
FileChannel channel = input.getChannel();
output.getChannel().transferFrom(channel, 0, channel.size());
}
+ destFile.setExecutable(srcFile.canExecute());
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
index 051a1d1c81..431944f9d4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
@@ -164,12 +164,7 @@ public class PrePushHook extends GitHook<String> {
*/
public void setRefs(Collection<RemoteRefUpdate> toRefs) {
StringBuilder b = new StringBuilder();
- boolean first = true;
for (RemoteRefUpdate u : toRefs) {
- if (!first)
- b.append("\n"); //$NON-NLS-1$
- else
- first = false;
b.append(u.getSrcRef());
b.append(" "); //$NON-NLS-1$
b.append(u.getNewObjectId().getName());
@@ -179,6 +174,7 @@ public class PrePushHook extends GitHook<String> {
ObjectId ooid = u.getExpectedOldObjectId();
b.append((ooid == null) ? ObjectId.zeroId().getName() : ooid
.getName());
+ b.append("\n"); //$NON-NLS-1$
}
refs = b.toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
index 9b255b41c3..41923eed18 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -443,7 +443,7 @@ public class Strings {
if (in_brackets > 0)
throw new InvalidPatternException("Not closed bracket?", pattern); //$NON-NLS-1$
try {
- return Pattern.compile(sb.toString());
+ return Pattern.compile(sb.toString(), Pattern.DOTALL);
} catch (PatternSyntaxException e) {
throw new InvalidPatternException(
MessageFormat.format(JGitText.get().invalidIgnoreRule,
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 4c60266248..5bbfe81dff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -534,6 +534,7 @@ public class JGitText extends TranslationBundle {
/***/ public String newlineInQuotesNotAllowed;
/***/ public String noApplyInDelete;
/***/ public String noClosingBracket;
+ /***/ public String noCommitsSelectedForShallow;
/***/ public String noCredentialsProvider;
/***/ public String noHEADExistsAndNoExplicitStartingRevisionWasSpecified;
/***/ public String noHMACsupport;
@@ -738,10 +739,8 @@ public class JGitText extends TranslationBundle {
/***/ public String submoduleNameInvalid;
/***/ public String submoduleParentRemoteUrlInvalid;
/***/ public String submodulePathInvalid;
- /***/ public String submodulesNotSupported;
/***/ public String submoduleUrlInvalid;
/***/ public String supportOnlyPackIndexVersion2;
- /***/ public String symlinkCannotBeWrittenAsTheLinkTarget;
/***/ public String systemConfigFileInvalid;
/***/ public String tagAlreadyExists;
/***/ public String tagNameInvalid;
@@ -842,7 +841,9 @@ public class JGitText extends TranslationBundle {
/***/ public String uriNotFoundWithMessage;
/***/ public String URINotSupported;
/***/ public String userConfigInvalid;
+ /***/ public String validatingGitModules;
/***/ public String walkFailure;
+ /***/ public String wantNoSpaceWithCapabilities;
/***/ public String wantNotValid;
/***/ public String weeksAgo;
/***/ public String windowSizeMustBeLesserThanLimit;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
index 131b0048ae..335ac667cc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
@@ -61,20 +61,21 @@ public class FsckError {
final int type;
- ObjectChecker.ErrorType errorType;
+ @Nullable
+ final ObjectChecker.ErrorType errorType;
/**
* @param id
* the object identifier.
* @param type
* type of the object.
+ * @param errorType
+ * kind of error
*/
- public CorruptObject(ObjectId id, int type) {
+ public CorruptObject(ObjectId id, int type,
+ @Nullable ObjectChecker.ErrorType errorType) {
this.id = id;
this.type = type;
- }
-
- void setErrorType(ObjectChecker.ErrorType errorType) {
this.errorType = errorType;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
index 5397ba4798..50594df1dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
@@ -174,12 +174,8 @@ public class FsckPackParser extends PackParser {
try {
super.verifySafeObject(id, type, data);
} catch (CorruptObjectException e) {
- // catch the exception and continue parse the pack file
- CorruptObject o = new CorruptObject(id.toObjectId(), type);
- if (e.getErrorType() != null) {
- o.setErrorType(e.getErrorType());
- }
- corruptObjects.add(o);
+ corruptObjects.add(
+ new CorruptObject(id.toObjectId(), type, e.getErrorType()));
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
index 3f96d0919b..c0e24c02cf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.internal.storage.dfs;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
@@ -54,12 +55,18 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.fsck.FsckError;
import org.eclipse.jgit.internal.fsck.FsckError.CorruptIndex;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
import org.eclipse.jgit.internal.fsck.FsckPackParser;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException;
+import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.GitmoduleEntry;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.ObjectWalk;
@@ -102,6 +109,7 @@ public class DfsFsck {
FsckError errors = new FsckError();
if (!connectivityOnly) {
+ objChecker.reset();
checkPacks(pm, errors);
}
checkConnectivity(pm, errors);
@@ -128,6 +136,8 @@ public class DfsFsck {
}
}
}
+
+ checkGitModules(pm, errors);
}
private void verifyPack(ProgressMonitor pm, FsckError errors, DfsReader ctx,
@@ -142,6 +152,28 @@ public class DfsFsck {
fpp.verifyIndex(pack.getPackIndex(ctx));
}
+ private void checkGitModules(ProgressMonitor pm, FsckError errors)
+ throws IOException {
+ pm.beginTask(JGitText.get().validatingGitModules,
+ objChecker.getGitsubmodules().size());
+ for (GitmoduleEntry entry : objChecker.getGitsubmodules()) {
+ AnyObjectId blobId = entry.getBlobId();
+ ObjectLoader blob = objdb.open(blobId, Constants.OBJ_BLOB);
+
+ try {
+ SubmoduleValidator.assertValidGitModulesFile(
+ new String(blob.getBytes(), UTF_8));
+ } catch (SubmoduleValidationException e) {
+ CorruptObject co = new FsckError.CorruptObject(
+ blobId.toObjectId(), Constants.OBJ_BLOB,
+ e.getFsckMessageId());
+ errors.getCorruptObjects().add(co);
+ }
+ pm.update(1);
+ }
+ pm.endTask();
+ }
+
private void checkConnectivity(ProgressMonitor pm, FsckError errors)
throws IOException {
pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
@@ -179,6 +211,9 @@ public class DfsFsck {
* Use a customized object checker instead of the default one. Caller can
* specify a skip list to ignore some errors.
*
+ * It will be reset at the start of each {{@link #check(ProgressMonitor)}
+ * call.
+ *
* @param objChecker
* A customized object checker.
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
index 9b98250884..d5e17224cf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
@@ -57,7 +57,7 @@ public interface ReadableChannel extends ReadableByteChannel {
* @throws java.io.IOException
* the channel's current position cannot be obtained.
*/
- public long position() throws IOException;
+ long position() throws IOException;
/**
* Seek the current position of the channel to a new offset.
@@ -70,7 +70,7 @@ public interface ReadableChannel extends ReadableByteChannel {
* channel only supports block aligned IO and the current
* position is not block aligned.
*/
- public void position(long newPosition) throws IOException;
+ void position(long newPosition) throws IOException;
/**
* Get the total size of the channel.
@@ -83,7 +83,7 @@ public interface ReadableChannel extends ReadableByteChannel {
* @throws java.io.IOException
* the size cannot be determined.
*/
- public long size() throws IOException;
+ long size() throws IOException;
/**
* Get the recommended alignment for reads.
@@ -102,7 +102,7 @@ public interface ReadableChannel extends ReadableByteChannel {
* @return recommended alignment size for randomly positioned reads. Does
* not need to be a power of 2.
*/
- public int blockSize();
+ int blockSize();
/**
* Recommend the channel maintain a read-ahead buffer.
@@ -131,5 +131,5 @@ public interface ReadableChannel extends ReadableByteChannel {
* @throws java.io.IOException
* if the read ahead cannot be adjusted.
*/
- public void setReadAheadBytes(int bufferSize) throws IOException;
+ void setReadAheadBytes(int bufferSize) 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 e35b9c9e4a..35522667e0 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
@@ -43,6 +43,7 @@
package org.eclipse.jgit.internal.storage.file;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
@@ -50,7 +51,6 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileReader;
import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
@@ -1036,8 +1036,8 @@ public class ObjectDirectory extends FileObjectDatabase {
}
private static BufferedReader open(File f)
- throws FileNotFoundException {
- return new BufferedReader(new FileReader(f));
+ throws IOException, FileNotFoundException {
+ return Files.newBufferedReader(f.toPath(), UTF_8);
}
private AlternateHandle openAlternate(String location)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
index f759e23ef4..2c806235a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
@@ -79,7 +79,7 @@ public interface ObjectReuseAsIs {
* the Git type of the object that will be packed.
* @return a new instance for this object.
*/
- public ObjectToPack newObjectToPack(AnyObjectId objectId, int type);
+ ObjectToPack newObjectToPack(AnyObjectId objectId, int type);
/**
* Select the best object representation for a packer.
@@ -114,7 +114,7 @@ public interface ObjectReuseAsIs {
* @throws java.io.IOException
* the repository cannot be accessed. Packing will abort.
*/
- public void selectObjectRepresentation(PackWriter packer,
+ void selectObjectRepresentation(PackWriter packer,
ProgressMonitor monitor, Iterable<ObjectToPack> objects)
throws IOException, MissingObjectException;
@@ -155,7 +155,7 @@ public interface ObjectReuseAsIs {
* the stream cannot be written to, or one or more required
* objects cannot be accessed from the object database.
*/
- public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
+ void writeObjects(PackOutputStream out, List<ObjectToPack> list)
throws IOException;
/**
@@ -200,7 +200,7 @@ public interface ObjectReuseAsIs {
* the stream's write method threw an exception. Packing will
* abort.
*/
- public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
+ void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
boolean validate) throws IOException,
StoredObjectRepresentationNotAvailableException;
@@ -216,7 +216,7 @@ public interface ObjectReuseAsIs {
* @throws java.io.IOException
* the pack cannot be read, or stream did not accept a write.
*/
- public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack)
+ void copyPackAsIs(PackOutputStream out, CachedPack pack)
throws IOException;
/**
@@ -234,6 +234,6 @@ public interface ObjectReuseAsIs {
* Callers may choose to ignore this and continue as-if there
* were no cached packs.
*/
- public Collection<CachedPack> getCachedPacksAndUpdate(
+ Collection<CachedPack> getCachedPacksAndUpdate(
BitmapBuilder needBitmap) throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
index 7f38a7b51a..eb777be809 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackOutputStream.java
@@ -187,6 +187,7 @@ public final class PackOutputStream extends OutputStream {
* @throws java.io.IOException
* the underlying stream refused to accept the header.
*/
+ @SuppressWarnings("ShortCircuitBoolean")
public final void writeHeader(ObjectToPack otp, long rawLength)
throws IOException {
ObjectToPack b = otp.getDeltaBase();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
index 3651631573..7b872b1860 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
@@ -45,13 +45,17 @@ package org.eclipse.jgit.internal.submodule;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PATH;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_URL;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_SUBMODULE_SECTION;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_NAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_PARSE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_PATH;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.GITMODULES_URL;
-import java.io.IOException;
import java.text.MessageFormat;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectChecker;
/**
* Validations for the git submodule fields (name, path, uri).
@@ -66,15 +70,30 @@ public class SubmoduleValidator {
*/
public static class SubmoduleValidationException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final ObjectChecker.ErrorType fsckMessageId;
+
/**
* @param message
* Description of the problem
+ * @param fsckMessageId
+ * Error identifier, following the git fsck fsck.<msg-id>
+ * format
*/
- public SubmoduleValidationException(String message) {
+ SubmoduleValidationException(String message,
+ ObjectChecker.ErrorType fsckMessageId) {
super(message);
+ this.fsckMessageId = fsckMessageId;
}
- private static final long serialVersionUID = 1L;
+
+ /**
+ * @return the error identifier
+ */
+ public ObjectChecker.ErrorType getFsckMessageId() {
+ return fsckMessageId;
+ }
}
/**
@@ -100,13 +119,15 @@ public class SubmoduleValidator {
// Since Path class is platform dependent, we manually check '/' and
// '\\' patterns here.
throw new SubmoduleValidationException(MessageFormat
- .format(JGitText.get().invalidNameContainsDotDot, name));
+ .format(JGitText.get().invalidNameContainsDotDot, name),
+ GITMODULES_NAME);
}
if (name.startsWith("-")) { //$NON-NLS-1$
throw new SubmoduleValidationException(
MessageFormat.format(
- JGitText.get().submoduleNameInvalid, name));
+ JGitText.get().submoduleNameInvalid, name),
+ GITMODULES_NAME);
}
}
@@ -123,7 +144,8 @@ public class SubmoduleValidator {
if (uri.startsWith("-")) { //$NON-NLS-1$
throw new SubmoduleValidationException(
MessageFormat.format(
- JGitText.get().submoduleUrlInvalid, uri));
+ JGitText.get().submoduleUrlInvalid, uri),
+ GITMODULES_URL);
}
}
@@ -140,19 +162,22 @@ public class SubmoduleValidator {
if (path.startsWith("-")) { //$NON-NLS-1$
throw new SubmoduleValidationException(
MessageFormat.format(
- JGitText.get().submodulePathInvalid, path));
+ JGitText.get().submodulePathInvalid, path),
+ GITMODULES_PATH);
}
}
/**
+ * Validate a .gitmodules file
+ *
* @param gitModulesContents
* Contents of a .gitmodule file. They will be parsed internally.
- * @throws IOException
- * If the contents
+ * @throws SubmoduleValidationException
+ * if the contents don't look like a configuration file or field
+ * values are not valid
*/
public static void assertValidGitModulesFile(String gitModulesContents)
- throws IOException {
- // Validate .gitmodules file
+ throws SubmoduleValidationException {
Config c = new Config();
try {
c.fromText(gitModulesContents);
@@ -173,12 +198,9 @@ public class SubmoduleValidator {
}
}
} catch (ConfigInvalidException e) {
- throw new IOException(
- MessageFormat.format(
- JGitText.get().invalidGitModules,
- e));
- } catch (SubmoduleValidationException e) {
- throw new IOException(e.getMessage(), e);
+ throw new SubmoduleValidationException(
+ JGitText.get().invalidGitModules,
+ GITMODULES_PARSE);
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java
new file mode 100644
index 0000000000..2dae021702
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.internal.transport.parser;
+
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * In the pack negotiation phase (protocol v0/v1), the client sends a list of
+ * wants. The first "want" line is special, as it (can) have a list of
+ * capabilities appended.
+ *
+ * E.g. "want oid cap1 cap2 cap3"
+ *
+ * Do not confuse this line with the first one in the reference advertisement,
+ * which is sent by the server, looks like
+ * "b8f7c471373b8583ced0025cfad8c9916c484b76 HEAD\0 cap1 cap2 cap3" and is
+ * parsed by the BasePackConnection.readAdvertisedRefs method.
+ *
+ * This class parses the input want line and holds the results: the actual want
+ * line and the capabilities.
+ *
+ * @since 5.2
+ */
+public class FirstWant {
+ private final String line;
+
+ private final Set<String> capabilities;
+
+ @Nullable
+ private final String agent;
+
+ private static final String AGENT_PREFIX = OPTION_AGENT + '=';
+
+ /**
+ * Parse the first want line in the protocol v0/v1 pack negotiation.
+ *
+ * @param line
+ * line from the client.
+ * @return an instance of FirstWant
+ * @throws PackProtocolException
+ * if the line doesn't follow the protocol format.
+ */
+ public static FirstWant fromLine(String line) throws PackProtocolException {
+ String wantLine;
+ Set<String> capabilities;
+ String agent = null;
+
+ if (line.length() > 45) {
+ String opt = line.substring(45);
+ if (!opt.startsWith(" ")) { //$NON-NLS-1$
+ throw new PackProtocolException(JGitText.get().wantNoSpaceWithCapabilities);
+ }
+ opt = opt.substring(1);
+
+ HashSet<String> opts = new HashSet<>();
+ for (String clientCapability : opt.split(" ")) { //$NON-NLS-1$
+ if (clientCapability.startsWith(AGENT_PREFIX)) {
+ agent = clientCapability.substring(AGENT_PREFIX.length());
+ } else {
+ opts.add(clientCapability);
+ }
+ }
+ wantLine = line.substring(0, 45);
+ capabilities = Collections.unmodifiableSet(opts);
+ } else {
+ wantLine = line;
+ capabilities = Collections.emptySet();
+ }
+
+ return new FirstWant(wantLine, capabilities, agent);
+ }
+
+ private FirstWant(String line, Set<String> capabilities,
+ @Nullable String agent) {
+ this.line = line;
+ this.capabilities = capabilities;
+ this.agent = agent;
+ }
+
+ /** @return non-capabilities part of the line. */
+ public String getLine() {
+ return line;
+ }
+
+ /**
+ * @return capabilities parsed from the line as an immutable set (excluding
+ * agent).
+ */
+ public Set<String> getCapabilities() {
+ return capabilities;
+ }
+
+ /** @return client user agent parsed from the line. */
+ @Nullable
+ public String getAgent() {
+ return agent;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
new file mode 100644
index 0000000000..c1e94a0a3e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -0,0 +1,924 @@
+/*
+ * Copyright (C) 2008, 2017, Google Inc.
+ * Copyright (C) 2017, 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.transport.ssh;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.fnmatch.FileNameMatcher;
+import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Fairly complete configuration parser for the openssh ~/.ssh/config file.
+ * <p>
+ * Both JSch 0.1.54 and Apache MINA sshd 2.1.0 have parsers for this, but both
+ * are buggy. Therefore we implement our own parser to read an openssh
+ * configuration file.
+ * </p>
+ * <p>
+ * Limitations compared to the full openssh 7.5 parser:
+ * </p>
+ * <ul>
+ * <li>This parser does not handle Match or Include keywords.
+ * <li>This parser does not do host name canonicalization.
+ * </ul>
+ * <p>
+ * Note that openssh's readconf.c is a validating parser; this parser does not
+ * validate entries.
+ * </p>
+ * <p>
+ * This config does %-substitutions for the following tokens:
+ * </p>
+ * <ul>
+ * <li>%% - single %
+ * <li>%C - short-hand for %l%h%p%r.
+ * <li>%d - home directory path
+ * <li>%h - remote host name
+ * <li>%L - local host name without domain
+ * <li>%l - FQDN of the local host
+ * <li>%n - host name as specified in {@link #lookup(String, int, String)}
+ * <li>%p - port number; if not given in {@link #lookup(String, int, String)}
+ * replaced only if set in the config
+ * <li>%r - remote user name; if not given in
+ * {@link #lookup(String, int, String)} replaced only if set in the config
+ * <li>%u - local user name
+ * </ul>
+ * <p>
+ * %i is not handled; Java has no concept of a "user ID". %T is always replaced
+ * by NONE.
+ * </p>
+ *
+ * @see <a href="http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5">man
+ * ssh-config</a>
+ */
+public class OpenSshConfigFile {
+
+ /**
+ * "Host" name of the HostEntry for the default options before the first
+ * host block in a config file.
+ */
+ private static final String DEFAULT_NAME = ""; //$NON-NLS-1$
+
+ /** The user's home directory, as key files may be relative to here. */
+ private final File home;
+
+ /** The .ssh/config file we read and monitor for updates. */
+ private final File configFile;
+
+ /** User name of the user on the host OS. */
+ private final String localUserName;
+
+ /** Modification time of {@link #configFile} when it was last loaded. */
+ private Instant lastModified;
+
+ /**
+ * Encapsulates entries read out of the configuration file, and a cache of
+ * fully resolved entries created from that.
+ */
+ private static class State {
+ // Keyed by pattern; if a "Host" line has multiple patterns, we generate
+ // duplicate HostEntry objects
+ Map<String, HostEntry> entries = new LinkedHashMap<>();
+
+ // Keyed by user@hostname:port
+ Map<String, HostEntry> hosts = new HashMap<>();
+
+ @Override
+ @SuppressWarnings("nls")
+ public String toString() {
+ return "State [entries=" + entries + ", hosts=" + hosts + "]";
+ }
+ }
+
+ /** State read from the config file, plus the cache. */
+ private State state;
+
+ /**
+ * Creates a new {@link OpenSshConfigFile} that will read the config from
+ * file {@code config} use the given file {@code home} as "home" directory.
+ *
+ * @param home
+ * user's home directory for the purpose of ~ replacement
+ * @param config
+ * file to load.
+ * @param localUserName
+ * user name of the current user on the local host OS
+ */
+ public OpenSshConfigFile(@NonNull File home, @NonNull File config,
+ @NonNull String localUserName) {
+ this.home = home;
+ this.configFile = config;
+ this.localUserName = localUserName;
+ state = new State();
+ }
+
+ /**
+ * Locate the configuration for a specific host request.
+ *
+ * @param hostName
+ * the name the user has supplied to the SSH tool. This may be a
+ * real host name, or it may just be a "Host" block in the
+ * configuration file.
+ * @param port
+ * the user supplied; <= 0 if none
+ * @param userName
+ * the user supplied, may be {@code null} or empty if none given
+ * @return r configuration for the requested name.
+ */
+ @NonNull
+ public HostEntry lookup(@NonNull String hostName, int port,
+ String userName) {
+ final State cache = refresh();
+ String cacheKey = toCacheKey(hostName, port, userName);
+ HostEntry h = cache.hosts.get(cacheKey);
+ if (h != null) {
+ return h;
+ }
+ HostEntry fullConfig = new HostEntry();
+ // Initialize with default entries at the top of the file, before the
+ // first Host block.
+ fullConfig.merge(cache.entries.get(DEFAULT_NAME));
+ for (Map.Entry<String, HostEntry> e : cache.entries.entrySet()) {
+ String pattern = e.getKey();
+ if (isHostMatch(pattern, hostName)) {
+ fullConfig.merge(e.getValue());
+ }
+ }
+ fullConfig.substitute(hostName, port, userName, localUserName, home);
+ cache.hosts.put(cacheKey, fullConfig);
+ return fullConfig;
+ }
+
+ @NonNull
+ private String toCacheKey(@NonNull String hostName, int port,
+ String userName) {
+ String key = hostName;
+ if (port > 0) {
+ key = key + ':' + Integer.toString(port);
+ }
+ if (userName != null && !userName.isEmpty()) {
+ key = userName + '@' + key;
+ }
+ return key;
+ }
+
+ private synchronized State refresh() {
+ final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
+ if (!mtime.equals(lastModified)) {
+ State newState = new State();
+ try (BufferedReader br = Files
+ .newBufferedReader(configFile.toPath(), UTF_8)) {
+ newState.entries = parse(br);
+ } catch (IOException | RuntimeException none) {
+ // Ignore -- we'll set and return an empty state
+ }
+ lastModified = mtime;
+ state = newState;
+ }
+ return state;
+ }
+
+ private Map<String, HostEntry> parse(BufferedReader reader)
+ throws IOException {
+ final Map<String, HostEntry> entries = new LinkedHashMap<>();
+ final List<HostEntry> current = new ArrayList<>(4);
+ String line;
+
+ // The man page doesn't say so, but the openssh parser (readconf.c)
+ // starts out in active mode and thus always applies any lines that
+ // occur before the first host block. We gather those options in a
+ // HostEntry for DEFAULT_NAME.
+ HostEntry defaults = new HostEntry();
+ current.add(defaults);
+ entries.put(DEFAULT_NAME, defaults);
+
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
+ continue;
+ }
+ String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
+ // Although the ssh-config man page doesn't say so, the openssh
+ // parser does allow quoted keywords.
+ String keyword = dequote(parts[0].trim());
+ // man 5 ssh-config says lines had the format "keyword arguments",
+ // with no indication that arguments were optional. However, let's
+ // not crap out on missing arguments. See bug 444319.
+ String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$
+
+ if (StringUtils.equalsIgnoreCase(SshConstants.HOST, keyword)) {
+ current.clear();
+ for (String name : parseList(argValue)) {
+ if (name == null || name.isEmpty()) {
+ // null should not occur, but better be safe than sorry.
+ continue;
+ }
+ HostEntry c = entries.get(name);
+ if (c == null) {
+ c = new HostEntry();
+ entries.put(name, c);
+ }
+ current.add(c);
+ }
+ continue;
+ }
+
+ if (current.isEmpty()) {
+ // We received an option outside of a Host block. We
+ // don't know who this should match against, so skip.
+ continue;
+ }
+
+ if (HostEntry.isListKey(keyword)) {
+ List<String> args = validate(keyword, parseList(argValue));
+ for (HostEntry entry : current) {
+ entry.setValue(keyword, args);
+ }
+ } else if (!argValue.isEmpty()) {
+ argValue = validate(keyword, dequote(argValue));
+ for (HostEntry entry : current) {
+ entry.setValue(keyword, argValue);
+ }
+ }
+ }
+
+ return entries;
+ }
+
+ /**
+ * Splits the argument into a list of whitespace-separated elements.
+ * Elements containing whitespace must be quoted and will be de-quoted.
+ *
+ * @param argument
+ * argument part of the configuration line as read from the
+ * config file
+ * @return a {@link List} of elements, possibly empty and possibly
+ * containing empty elements, but not containing {@code null}
+ */
+ private List<String> parseList(String argument) {
+ List<String> result = new ArrayList<>(4);
+ int start = 0;
+ int length = argument.length();
+ while (start < length) {
+ // Skip whitespace
+ if (Character.isSpaceChar(argument.charAt(start))) {
+ start++;
+ continue;
+ }
+ if (argument.charAt(start) == '"') {
+ int stop = argument.indexOf('"', ++start);
+ if (stop < start) {
+ // No closing double quote: skip
+ break;
+ }
+ result.add(argument.substring(start, stop));
+ start = stop + 1;
+ } else {
+ int stop = start + 1;
+ while (stop < length
+ && !Character.isSpaceChar(argument.charAt(stop))) {
+ stop++;
+ }
+ result.add(argument.substring(start, stop));
+ start = stop + 1;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Hook to perform validation on a single value, or to sanitize it. If this
+ * throws an (unchecked) exception, parsing of the file is abandoned.
+ *
+ * @param key
+ * of the entry
+ * @param value
+ * as read from the config file
+ * @return the validated and possibly sanitized value
+ */
+ protected String validate(String key, String value) {
+ if (String.CASE_INSENSITIVE_ORDER.compare(key,
+ SshConstants.PREFERRED_AUTHENTICATIONS) == 0) {
+ return stripWhitespace(value);
+ }
+ return value;
+ }
+
+ /**
+ * Hook to perform validation on values, or to sanitize them. If this throws
+ * an (unchecked) exception, parsing of the file is abandoned.
+ *
+ * @param key
+ * of the entry
+ * @param value
+ * list of arguments as read from the config file
+ * @return a {@link List} of values, possibly empty and possibly containing
+ * empty elements, but not containing {@code null}
+ */
+ protected List<String> validate(String key, List<String> value) {
+ return value;
+ }
+
+ private static boolean isHostMatch(String pattern, String name) {
+ if (pattern.startsWith("!")) { //$NON-NLS-1$
+ return !patternMatchesHost(pattern.substring(1), name);
+ } else {
+ return patternMatchesHost(pattern, name);
+ }
+ }
+
+ private static boolean patternMatchesHost(String pattern, String name) {
+ if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
+ final FileNameMatcher fn;
+ try {
+ fn = new FileNameMatcher(pattern, null);
+ } catch (InvalidPatternException e) {
+ return false;
+ }
+ fn.append(name);
+ return fn.isMatch();
+ } else {
+ // Not a pattern but a full host name
+ return pattern.equals(name);
+ }
+ }
+
+ private static String dequote(String value) {
+ if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$
+ && value.length() > 1)
+ return value.substring(1, value.length() - 1);
+ return value;
+ }
+
+ private static String stripWhitespace(String value) {
+ final StringBuilder b = new StringBuilder();
+ for (int i = 0; i < value.length(); i++) {
+ if (!Character.isSpaceChar(value.charAt(i)))
+ b.append(value.charAt(i));
+ }
+ return b.toString();
+ }
+
+ private static File toFile(String path, File home) {
+ if (path.startsWith("~/") || path.startsWith("~" + File.separator)) { //$NON-NLS-1$ //$NON-NLS-2$
+ return new File(home, path.substring(2));
+ }
+ File ret = new File(path);
+ if (ret.isAbsolute()) {
+ return ret;
+ }
+ return new File(home, path);
+ }
+
+ /**
+ * Converts a positive value into an {@code int}.
+ *
+ * @param value
+ * to convert
+ * @return the value, or -1 if it wasn't a positive integral value
+ */
+ public static int positive(String value) {
+ if (value != null) {
+ try {
+ return Integer.parseUnsignedInt(value);
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Converts a ssh config flag value (yes/true/on - no/false/off) into an
+ * {@code boolean}.
+ *
+ * @param value
+ * to convert
+ * @return {@code true} if {@code value} is "yes", "on", or "true";
+ * {@code false} otherwise
+ */
+ public static boolean flag(String value) {
+ if (value == null) {
+ return false;
+ }
+ return SshConstants.YES.equals(value) || SshConstants.ON.equals(value)
+ || SshConstants.TRUE.equals(value);
+ }
+
+ /**
+ * Retrieves the local user name as given in the constructor.
+ *
+ * @return the user name
+ */
+ public String getLocalUserName() {
+ return localUserName;
+ }
+
+ /**
+ * A host entry from the ssh config file. Any merging of global values and
+ * of several matching host entries, %-substitutions, and ~ replacement have
+ * all been done.
+ */
+ public static class HostEntry {
+
+ /**
+ * Keys that can be specified multiple times, building up a list. (I.e.,
+ * those are the keys that do not follow the general rule of "first
+ * occurrence wins".)
+ */
+ private static final Set<String> MULTI_KEYS = new TreeSet<>(
+ String.CASE_INSENSITIVE_ORDER);
+
+ static {
+ MULTI_KEYS.add(SshConstants.CERTIFICATE_FILE);
+ MULTI_KEYS.add(SshConstants.IDENTITY_FILE);
+ MULTI_KEYS.add(SshConstants.LOCAL_FORWARD);
+ MULTI_KEYS.add(SshConstants.REMOTE_FORWARD);
+ MULTI_KEYS.add(SshConstants.SEND_ENV);
+ }
+
+ /**
+ * Keys that take a whitespace-separated list of elements as argument.
+ * Because the dequote-handling is different, we must handle those in
+ * the parser. There are a few other keys that take comma-separated
+ * lists as arguments, but for the parser those are single arguments
+ * that must be quoted if they contain whitespace, and taking them apart
+ * is the responsibility of the user of those keys.
+ */
+ private static final Set<String> LIST_KEYS = new TreeSet<>(
+ String.CASE_INSENSITIVE_ORDER);
+
+ static {
+ LIST_KEYS.add(SshConstants.CANONICAL_DOMAINS);
+ LIST_KEYS.add(SshConstants.GLOBAL_KNOWN_HOSTS_FILE);
+ LIST_KEYS.add(SshConstants.SEND_ENV);
+ LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE);
+ }
+
+ private Map<String, String> options;
+
+ private Map<String, List<String>> multiOptions;
+
+ private Map<String, List<String>> listOptions;
+
+ /**
+ * Retrieves the value of a single-valued key, or the first is the key
+ * has multiple values. Keys are case-insensitive, so
+ * {@code getValue("HostName") == getValue("HOSTNAME")}.
+ *
+ * @param key
+ * to get the value of
+ * @return the value, or {@code null} if none
+ */
+ public String getValue(String key) {
+ String result = options != null ? options.get(key) : null;
+ if (result == null) {
+ // Let's be lenient and return at least the first value from
+ // a list-valued or multi-valued key.
+ List<String> values = listOptions != null ? listOptions.get(key)
+ : null;
+ if (values == null) {
+ values = multiOptions != null ? multiOptions.get(key)
+ : null;
+ }
+ if (values != null && !values.isEmpty()) {
+ result = values.get(0);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the values of a multi or list-valued key. Keys are
+ * case-insensitive, so
+ * {@code getValue("HostName") == getValue("HOSTNAME")}.
+ *
+ * @param key
+ * to get the values of
+ * @return a possibly empty list of values
+ */
+ public List<String> getValues(String key) {
+ List<String> values = listOptions != null ? listOptions.get(key)
+ : null;
+ if (values == null) {
+ values = multiOptions != null ? multiOptions.get(key) : null;
+ }
+ if (values == null || values.isEmpty()) {
+ return new ArrayList<>();
+ }
+ return new ArrayList<>(values);
+ }
+
+ /**
+ * Sets the value of a single-valued key if it not set yet, or adds a
+ * value to a multi-valued key. If the value is {@code null}, the key is
+ * removed altogether, whether it is single-, list-, or multi-valued.
+ *
+ * @param key
+ * to modify
+ * @param value
+ * to set or add
+ */
+ public void setValue(String key, String value) {
+ if (value == null) {
+ if (multiOptions != null) {
+ multiOptions.remove(key);
+ }
+ if (listOptions != null) {
+ listOptions.remove(key);
+ }
+ if (options != null) {
+ options.remove(key);
+ }
+ return;
+ }
+ if (MULTI_KEYS.contains(key)) {
+ if (multiOptions == null) {
+ multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ List<String> values = multiOptions.get(key);
+ if (values == null) {
+ values = new ArrayList<>(4);
+ multiOptions.put(key, values);
+ }
+ values.add(value);
+ } else {
+ if (options == null) {
+ options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ if (!options.containsKey(key)) {
+ options.put(key, value);
+ }
+ }
+ }
+
+ /**
+ * Sets the values of a multi- or list-valued key.
+ *
+ * @param key
+ * to set
+ * @param values
+ * a non-empty list of values
+ */
+ public void setValue(String key, List<String> values) {
+ if (values.isEmpty()) {
+ return;
+ }
+ // Check multi-valued keys first; because of the replacement
+ // strategy, they must take precedence over list-valued keys
+ // which always follow the "first occurrence wins" strategy.
+ //
+ // Note that SendEnv is a multi-valued list-valued key. (It's
+ // rather immaterial for JGit, though.)
+ if (MULTI_KEYS.contains(key)) {
+ if (multiOptions == null) {
+ multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ List<String> items = multiOptions.get(key);
+ if (items == null) {
+ items = new ArrayList<>(values);
+ multiOptions.put(key, items);
+ } else {
+ items.addAll(values);
+ }
+ } else {
+ if (listOptions == null) {
+ listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ if (!listOptions.containsKey(key)) {
+ listOptions.put(key, values);
+ }
+ }
+ }
+
+ /**
+ * Does the key take a whitespace-separated list of values?
+ *
+ * @param key
+ * to check
+ * @return {@code true} if the key is a list-valued key.
+ */
+ public static boolean isListKey(String key) {
+ return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
+ }
+
+ void merge(HostEntry entry) {
+ if (entry == null) {
+ // Can occur if we could not read the config file
+ return;
+ }
+ if (entry.options != null) {
+ if (options == null) {
+ options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ for (Map.Entry<String, String> item : entry.options
+ .entrySet()) {
+ if (!options.containsKey(item.getKey())) {
+ options.put(item.getKey(), item.getValue());
+ }
+ }
+ }
+ if (entry.listOptions != null) {
+ if (listOptions == null) {
+ listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ for (Map.Entry<String, List<String>> item : entry.listOptions
+ .entrySet()) {
+ if (!listOptions.containsKey(item.getKey())) {
+ listOptions.put(item.getKey(), item.getValue());
+ }
+ }
+
+ }
+ if (entry.multiOptions != null) {
+ if (multiOptions == null) {
+ multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ for (Map.Entry<String, List<String>> item : entry.multiOptions
+ .entrySet()) {
+ List<String> values = multiOptions.get(item.getKey());
+ if (values == null) {
+ values = new ArrayList<>(item.getValue());
+ multiOptions.put(item.getKey(), values);
+ } else {
+ values.addAll(item.getValue());
+ }
+ }
+ }
+ }
+
+ private List<String> substitute(List<String> values, String allowed,
+ Replacer r) {
+ List<String> result = new ArrayList<>(values.size());
+ for (String value : values) {
+ result.add(r.substitute(value, allowed));
+ }
+ return result;
+ }
+
+ private List<String> replaceTilde(List<String> values, File home) {
+ List<String> result = new ArrayList<>(values.size());
+ for (String value : values) {
+ result.add(toFile(value, home).getPath());
+ }
+ return result;
+ }
+
+ void substitute(String originalHostName, int port, String userName,
+ String localUserName, File home) {
+ int p = port >= 0 ? port : positive(getValue(SshConstants.PORT));
+ if (p < 0) {
+ p = SshConstants.SSH_DEFAULT_PORT;
+ }
+ String u = userName != null && !userName.isEmpty() ? userName
+ : getValue(SshConstants.USER);
+ if (u == null || u.isEmpty()) {
+ u = localUserName;
+ }
+ Replacer r = new Replacer(originalHostName, p, u, localUserName,
+ home);
+ if (options != null) {
+ // HOSTNAME first
+ String hostName = options.get(SshConstants.HOST_NAME);
+ if (hostName == null || hostName.isEmpty()) {
+ options.put(SshConstants.HOST_NAME, originalHostName);
+ } else {
+ hostName = r.substitute(hostName, "h"); //$NON-NLS-1$
+ options.put(SshConstants.HOST_NAME, hostName);
+ r.update('h', hostName);
+ }
+ }
+ if (multiOptions != null) {
+ List<String> values = multiOptions
+ .get(SshConstants.IDENTITY_FILE);
+ if (values != null) {
+ values = substitute(values, "dhlru", r); //$NON-NLS-1$
+ values = replaceTilde(values, home);
+ multiOptions.put(SshConstants.IDENTITY_FILE, values);
+ }
+ values = multiOptions.get(SshConstants.CERTIFICATE_FILE);
+ if (values != null) {
+ values = substitute(values, "dhlru", r); //$NON-NLS-1$
+ values = replaceTilde(values, home);
+ multiOptions.put(SshConstants.CERTIFICATE_FILE, values);
+ }
+ }
+ if (listOptions != null) {
+ List<String> values = listOptions
+ .get(SshConstants.USER_KNOWN_HOSTS_FILE);
+ if (values != null) {
+ values = replaceTilde(values, home);
+ listOptions.put(SshConstants.USER_KNOWN_HOSTS_FILE, values);
+ }
+ }
+ if (options != null) {
+ // HOSTNAME already done above
+ String value = options.get(SshConstants.IDENTITY_AGENT);
+ if (value != null) {
+ value = r.substitute(value, "dhlru"); //$NON-NLS-1$
+ value = toFile(value, home).getPath();
+ options.put(SshConstants.IDENTITY_AGENT, value);
+ }
+ value = options.get(SshConstants.CONTROL_PATH);
+ if (value != null) {
+ value = r.substitute(value, "ChLlnpru"); //$NON-NLS-1$
+ value = toFile(value, home).getPath();
+ options.put(SshConstants.CONTROL_PATH, value);
+ }
+ value = options.get(SshConstants.LOCAL_COMMAND);
+ if (value != null) {
+ value = r.substitute(value, "CdhlnprTu"); //$NON-NLS-1$
+ options.put(SshConstants.LOCAL_COMMAND, value);
+ }
+ value = options.get(SshConstants.REMOTE_COMMAND);
+ if (value != null) {
+ value = r.substitute(value, "Cdhlnpru"); //$NON-NLS-1$
+ options.put(SshConstants.REMOTE_COMMAND, value);
+ }
+ value = options.get(SshConstants.PROXY_COMMAND);
+ if (value != null) {
+ value = r.substitute(value, "hpr"); //$NON-NLS-1$
+ options.put(SshConstants.PROXY_COMMAND, value);
+ }
+ }
+ // Match is not implemented and would need to be done elsewhere
+ // anyway.
+ }
+
+ /**
+ * Retrieves an unmodifiable map of all single-valued options, with
+ * case-insensitive lookup by keys.
+ *
+ * @return all single-valued options
+ */
+ @NonNull
+ public Map<String, String> getOptions() {
+ if (options == null) {
+ return Collections.emptyMap();
+ }
+ return Collections.unmodifiableMap(options);
+ }
+
+ /**
+ * Retrieves an unmodifiable map of all multi-valued options, with
+ * case-insensitive lookup by keys.
+ *
+ * @return all multi-valued options
+ */
+ @NonNull
+ public Map<String, List<String>> getMultiValuedOptions() {
+ if (listOptions == null && multiOptions == null) {
+ return Collections.emptyMap();
+ }
+ Map<String, List<String>> allValues = new TreeMap<>(
+ String.CASE_INSENSITIVE_ORDER);
+ if (multiOptions != null) {
+ allValues.putAll(multiOptions);
+ }
+ if (listOptions != null) {
+ allValues.putAll(listOptions);
+ }
+ return Collections.unmodifiableMap(allValues);
+ }
+
+ @Override
+ @SuppressWarnings("nls")
+ public String toString() {
+ return "HostEntry [options=" + options + ", multiOptions="
+ + multiOptions + ", listOptions=" + listOptions + "]";
+ }
+ }
+
+ private static class Replacer {
+ private final Map<Character, String> replacements = new HashMap<>();
+
+ public Replacer(String host, int port, String user,
+ String localUserName, File home) {
+ replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$
+ replacements.put(Character.valueOf('d'), home.getPath());
+ replacements.put(Character.valueOf('h'), host);
+ String localhost = SystemReader.getInstance().getHostname();
+ replacements.put(Character.valueOf('l'), localhost);
+ int period = localhost.indexOf('.');
+ if (period > 0) {
+ localhost = localhost.substring(0, period);
+ }
+ replacements.put(Character.valueOf('L'), localhost);
+ replacements.put(Character.valueOf('n'), host);
+ replacements.put(Character.valueOf('p'), Integer.toString(port));
+ replacements.put(Character.valueOf('r'), user == null ? "" : user); //$NON-NLS-1$
+ replacements.put(Character.valueOf('u'), localUserName);
+ replacements.put(Character.valueOf('C'),
+ substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
+ replacements.put(Character.valueOf('T'), "NONE"); //$NON-NLS-1$
+ }
+
+ public void update(char key, String value) {
+ replacements.put(Character.valueOf(key), value);
+ if ("lhpr".indexOf(key) >= 0) { //$NON-NLS-1$
+ replacements.put(Character.valueOf('C'),
+ substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ public String substitute(String input, String allowed) {
+ if (input == null || input.length() <= 1
+ || input.indexOf('%') < 0) {
+ return input;
+ }
+ StringBuilder builder = new StringBuilder();
+ int start = 0;
+ int length = input.length();
+ while (start < length) {
+ int percent = input.indexOf('%', start);
+ if (percent < 0 || percent + 1 >= length) {
+ builder.append(input.substring(start));
+ break;
+ }
+ String replacement = null;
+ char ch = input.charAt(percent + 1);
+ if (ch == '%' || allowed.indexOf(ch) >= 0) {
+ replacement = replacements.get(Character.valueOf(ch));
+ }
+ if (replacement == null) {
+ builder.append(input.substring(start, percent + 2));
+ } else {
+ builder.append(input.substring(start, percent))
+ .append(replacement);
+ }
+ start = percent + 2;
+ }
+ return builder.toString();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @SuppressWarnings("nls")
+ public String toString() {
+ return "OpenSshConfig [home=" + home + ", configFile=" + configFile
+ + ", lastModified=" + lastModified + ", state=" + state + "]";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java
index b4ea0e907f..659c67c5ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectLoaderQueue.java
@@ -78,7 +78,7 @@ public interface AsyncObjectLoaderQueue<T extends ObjectId> extends
* @throws java.io.IOException
* the object store cannot be accessed.
*/
- public boolean next() throws MissingObjectException, IOException;
+ boolean next() throws MissingObjectException, IOException;
/**
* Get the current object, null if the implementation lost track.
@@ -87,14 +87,14 @@ public interface AsyncObjectLoaderQueue<T extends ObjectId> extends
* Implementations may for performance reasons discard the caller's
* ObjectId and provider their own through {@link #getObjectId()}.
*/
- public T getCurrent();
+ T getCurrent();
/**
* Get the ObjectId of the current object. Never null.
*
* @return the ObjectId of the current object. Never null.
*/
- public ObjectId getObjectId();
+ ObjectId getObjectId();
/**
* Obtain a loader to read the object.
@@ -115,5 +115,5 @@ public interface AsyncObjectLoaderQueue<T extends ObjectId> extends
* @throws java.io.IOException
* the object store cannot be accessed.
*/
- public ObjectLoader open() throws IOException;
+ ObjectLoader open() throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java
index 03efcd295e..6b8642f119 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncObjectSizeQueue.java
@@ -73,7 +73,7 @@ public interface AsyncObjectSizeQueue<T extends ObjectId> extends
* @throws java.io.IOException
* the object store cannot be accessed.
*/
- public boolean next() throws MissingObjectException, IOException;
+ boolean next() throws MissingObjectException, IOException;
/**
* <p>getCurrent.</p>
@@ -82,19 +82,19 @@ public interface AsyncObjectSizeQueue<T extends ObjectId> extends
* Implementations may for performance reasons discard the caller's
* ObjectId and provider their own through {@link #getObjectId()}.
*/
- public T getCurrent();
+ T getCurrent();
/**
* Get the ObjectId of the current object. Never null.
*
* @return the ObjectId of the current object. Never null.
*/
- public ObjectId getObjectId();
+ ObjectId getObjectId();
/**
* Get the size of the current object.
*
* @return the size of the current object.
*/
- public long getSize();
+ long getSize();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java
index 00555b0907..27b9c2038a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AsyncOperation.java
@@ -68,10 +68,10 @@ public interface AsyncOperation {
* @return false if the task could not be cancelled, typically because it
* has already completed normally; true otherwise
*/
- public boolean cancel(boolean mayInterruptIfRunning);
+ boolean cancel(boolean mayInterruptIfRunning);
/**
* Release resources used by the operation, including cancellation.
*/
- public void release();
+ void release();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
index 3fa3168327..7878351ce8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
@@ -55,7 +55,7 @@ import org.eclipse.jgit.errors.CorruptObjectException;
*/
public interface BlobObjectChecker {
/** No-op implementation of {@link BlobObjectChecker}. */
- public static final BlobObjectChecker NULL_CHECKER =
+ BlobObjectChecker NULL_CHECKER =
new BlobObjectChecker() {
@Override
public void update(byte[] in, int p, int len) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
index cfc0cc86d1..84ff0a8936 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
@@ -12,13 +12,13 @@ public interface CheckoutEntry {
*
* @return the name of the branch before checkout
*/
- public abstract String getFromBranch();
+ String getFromBranch();
/**
* Get the name of the branch after checkout
*
* @return the name of the branch after checkout
*/
- public abstract String getToBranch();
+ String getToBranch();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index b666f21d0b..4726975d07 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -62,7 +62,6 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
-import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ConfigChangedEvent;
import org.eclipse.jgit.events.ConfigChangedListener;
@@ -868,7 +867,7 @@ public class Config {
boolean lastWasMatch = false;
for (ConfigLine e : srcState.entryList) {
- if (e.match(section, subsection)) {
+ if (e.includedFrom == null && e.match(section, subsection)) {
// Skip this record, it's for the section we are removing.
lastWasMatch = true;
continue;
@@ -923,7 +922,7 @@ public class Config {
//
while (entryIndex < entries.size() && valueIndex < values.size()) {
final ConfigLine e = entries.get(entryIndex);
- if (e.match(section, subsection, name)) {
+ if (e.includedFrom == null && e.match(section, subsection, name)) {
entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
insertPosition = entryIndex + 1;
}
@@ -935,7 +934,8 @@ public class Config {
if (valueIndex == values.size() && entryIndex < entries.size()) {
while (entryIndex < entries.size()) {
final ConfigLine e = entries.get(entryIndex++);
- if (e.match(section, subsection, name))
+ if (e.includedFrom == null
+ && e.match(section, subsection, name))
entries.remove(--entryIndex);
}
}
@@ -948,7 +948,8 @@ public class Config {
// is already a section available that matches. Insert
// after the last key of that section.
//
- insertPosition = findSectionEnd(entries, section, subsection);
+ insertPosition = findSectionEnd(entries, section, subsection,
+ true);
}
if (insertPosition < 0) {
// We didn't find any matching section header for this key,
@@ -985,9 +986,14 @@ public class Config {
}
private static int findSectionEnd(final List<ConfigLine> entries,
- final String section, final String subsection) {
+ final String section, final String subsection,
+ boolean skipIncludedLines) {
for (int i = 0; i < entries.size(); i++) {
ConfigLine e = entries.get(i);
+ if (e.includedFrom != null && skipIncludedLines) {
+ continue;
+ }
+
if (e.match(section, subsection, null)) {
i++;
while (i < entries.size()) {
@@ -1011,6 +1017,8 @@ public class Config {
public String toText() {
final StringBuilder out = new StringBuilder();
for (ConfigLine e : state.get().entryList) {
+ if (e.includedFrom != null)
+ continue;
if (e.prefix != null)
out.append(e.prefix);
if (e.section != null && e.name == null) {
@@ -1060,11 +1068,11 @@ public class Config {
* made to {@code this}.
*/
public void fromText(String text) throws ConfigInvalidException {
- state.set(newState(fromTextRecurse(text, 1)));
+ state.set(newState(fromTextRecurse(text, 1, null)));
}
- private List<ConfigLine> fromTextRecurse(String text, int depth)
- throws ConfigInvalidException {
+ private List<ConfigLine> fromTextRecurse(String text, int depth,
+ String includedFrom) throws ConfigInvalidException {
if (depth > MAX_DEPTH) {
throw new ConfigInvalidException(
JGitText.get().tooManyIncludeRecursions);
@@ -1073,6 +1081,7 @@ public class Config {
final StringReader in = new StringReader(text);
ConfigLine last = null;
ConfigLine e = new ConfigLine();
+ e.includedFrom = includedFrom;
for (;;) {
int input = in.read();
if (-1 == input) {
@@ -1088,7 +1097,7 @@ public class Config {
if (e.section != null)
last = e;
e = new ConfigLine();
-
+ e.includedFrom = includedFrom;
} else if (e.suffix != null) {
// Everything up until the end-of-line is in the suffix.
e.suffix += c;
@@ -1148,7 +1157,6 @@ public class Config {
* if something went wrong while reading the config
* @since 4.10
*/
- @Nullable
protected byte[] readIncludedConfig(String relPath)
throws ConfigInvalidException {
return null;
@@ -1173,7 +1181,7 @@ public class Config {
decoded = RawParseUtils.decode(bytes);
}
try {
- newEntries.addAll(fromTextRecurse(decoded, depth + 1));
+ newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
} catch (ConfigInvalidException e) {
throw new ConfigInvalidException(MessageFormat
.format(JGitText.get().cannotReadFile, line.value), e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 82ccd7b034..5ae9d41db2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -119,6 +119,36 @@ public final class ConfigConstants {
*/
public static final String CONFIG_FILTER_SECTION = "filter";
+ /**
+ * The "gpg" section
+ * @since 5.2
+ */
+ public static final String CONFIG_GPG_SECTION = "gpg";
+
+ /**
+ * The "format" key
+ * @since 5.2
+ */
+ public static final String CONFIG_KEY_FORMAT = "format";
+
+ /**
+ * The "signingKey" key
+ * @since 5.2
+ */
+ public static final String CONFIG_KEY_SIGNINGKEY = "signingKey";
+
+ /**
+ * The "commit" section
+ * @since 5.2
+ */
+ public static final String CONFIG_COMMIT_SECTION = "commit";
+
+ /**
+ * The "gpgSign" key
+ * @since 5.2
+ */
+ public static final String CONFIG_KEY_GPGSIGN = "gpgSign";
+
/** The "algorithm" key */
public static final String CONFIG_KEY_ALGORITHM = "algorithm";
@@ -434,6 +464,20 @@ public final class ConfigConstants {
public static final String CONFIG_SECTION_LFS = "lfs";
/**
+ * The "i18n" section
+ *
+ * @since 5.2
+ */
+ public static final String CONFIG_SECTION_I18N = "i18n";
+
+ /**
+ * The "logOutputEncoding" key
+ *
+ * @since 5.2
+ */
+ public static final String CONFIG_KEY_LOG_OUTPUT_ENCODING = "logOutputEncoding";
+
+ /**
* The "filesystem" section
* @since 5.1.9
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
index 937ba925c5..e623a8cebc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
@@ -73,6 +73,9 @@ class ConfigLine {
/** The text content after entry. */
String suffix;
+ /** The source from which this line was included from. */
+ String includedFrom;
+
ConfigLine forValue(String newValue) {
final ConfigLine e = new ConfigLine();
e.prefix = prefix;
@@ -81,6 +84,7 @@ class ConfigLine {
e.name = name;
e.value = newValue;
e.suffix = suffix;
+ e.includedFrom = includedFrom;
return e;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 001ae93a0c..fb239399ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -301,7 +301,8 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
/** {@inheritDoc} */
@Override
- public @NonNull List<RefSpec> getRefSpecs(Config config, String section,
+ @NonNull
+ public List<RefSpec> getRefSpecs(Config config, String section,
String subsection, String name) {
String[] values = config.getStringList(section, subsection, name);
List<RefSpec> result = new ArrayList<>(values.length);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
new file mode 100644
index 0000000000..a09bc00786
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018, Salesforce.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lib;
+
+/**
+ * Typed access to GPG related configuration options.
+ *
+ * @since 5.2
+ */
+public class GpgConfig {
+
+ /**
+ * Config values for gpg.format.
+ */
+ public enum GpgFormat implements Config.ConfigEnum {
+
+ /** Value for openpgp */
+ OPENPGP("openpgp"), //$NON-NLS-1$
+ /** Value for x509 */
+ X509("x509"); //$NON-NLS-1$
+
+ private final String configValue;
+
+ private GpgFormat(String configValue) {
+ this.configValue = configValue;
+ }
+
+ @Override
+ public boolean matchConfigValue(String s) {
+ return configValue.equals(s);
+ }
+
+ @Override
+ public String toConfigValue() {
+ return configValue;
+ }
+ }
+
+ private final Config config;
+
+ /**
+ * Create a new GPG config, which will read configuration from config.
+ *
+ * @param config
+ * the config to read from
+ */
+ public GpgConfig(Config config) {
+ this.config = config;
+ }
+
+ /**
+ * Retrieves the config value of gpg.format.
+ *
+ * @return the {@link org.eclipse.jgit.lib.GpgConfig.GpgFormat}
+ */
+ public GpgFormat getKeyFormat() {
+ return config.getEnum(GpgFormat.values(),
+ ConfigConstants.CONFIG_GPG_SECTION, null,
+ ConfigConstants.CONFIG_KEY_FORMAT, GpgFormat.OPENPGP);
+ }
+
+ /**
+ * Retrieves the config value of user.signingKey.
+ *
+ * @return the value of user.signingKey (may be <code>null</code>)
+ */
+ public String getSigningKey() {
+ return config.getString(ConfigConstants.CONFIG_USER_SECTION, null,
+ ConfigConstants.CONFIG_KEY_SIGNINGKEY);
+ }
+
+ /**
+ * Retrieves the config value of commit.gpgSign.
+ *
+ * @return the value of commit.gpgSign (defaults to <code>false</code>)
+ */
+ public boolean isSignCommits() {
+ return config.getBoolean(ConfigConstants.CONFIG_COMMIT_SECTION,
+ ConfigConstants.CONFIG_KEY_GPGSIGN, false);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 94b9ddc188..f37c310752 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -47,7 +47,11 @@
package org.eclipse.jgit.lib;
+import java.io.File;
import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -261,6 +265,8 @@ public class IndexDiff {
private Set<String> missing = new HashSet<>();
+ private Set<String> missingSubmodules = new HashSet<>();
+
private Set<String> modified = new HashSet<>();
private Set<String> untracked = new HashSet<>();
@@ -501,9 +507,15 @@ public class IndexDiff {
if (dirCacheIterator != null) {
if (workingTreeIterator == null) {
// in index, not in workdir => missing
- if (!isEntryGitLink(dirCacheIterator)
- || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
- missing.add(treeWalk.getPathString());
+ boolean isGitLink = isEntryGitLink(dirCacheIterator);
+ if (!isGitLink
+ || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
+ String path = treeWalk.getPathString();
+ missing.add(path);
+ if (isGitLink) {
+ missingSubmodules.add(path);
+ }
+ }
} else {
if (workingTreeIterator.isModified(
dirCacheIterator.getDirCacheEntry(), true,
@@ -543,8 +555,8 @@ public class IndexDiff {
smw.getPath()), e);
}
try (Repository subRepo = smw.getRepository()) {
+ String subRepoPath = smw.getPath();
if (subRepo != null) {
- String subRepoPath = smw.getPath();
ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
if (subHead != null
&& !subHead.equals(smw.getObjectId())) {
@@ -573,6 +585,21 @@ public class IndexDiff {
recordFileMode(subRepoPath, FileMode.GITLINK);
}
}
+ } else if (missingSubmodules.remove(subRepoPath)) {
+ // If the directory is there and empty but the submodule
+ // repository in .git/modules doesn't exist yet it isn't
+ // "missing".
+ File gitDir = new File(
+ new File(repository.getDirectory(),
+ Constants.MODULES),
+ subRepoPath);
+ if (!gitDir.isDirectory()) {
+ File dir = SubmoduleWalk.getSubmoduleDirectory(
+ repository, subRepoPath);
+ if (dir.isDirectory() && !hasFiles(dir)) {
+ missing.remove(subRepoPath);
+ }
+ }
}
}
}
@@ -592,6 +619,15 @@ public class IndexDiff {
return true;
}
+ private boolean hasFiles(File directory) {
+ try (DirectoryStream<java.nio.file.Path> dir = Files
+ .newDirectoryStream(directory.toPath())) {
+ return dir.iterator().hasNext();
+ } catch (DirectoryIteratorException | IOException e) {
+ return false;
+ }
+ }
+
private void recordFileMode(String path, FileMode mode) {
Set<String> values = fileModes.get(mode);
if (path != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index d37fb21c93..127f019c46 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -109,7 +109,8 @@ import org.eclipse.jgit.util.StringUtils;
* the caller can provide both of these validations on its own.
* <p>
* Instances of this class are not thread safe, but they may be reused to
- * perform multiple object validations.
+ * perform multiple object validations, calling {@link #reset()} between them to
+ * clear the internal state (e.g. {@link #getGitsubmodules()})
*/
public class ObjectChecker {
/** Header "tree " */
@@ -173,6 +174,13 @@ public class ObjectChecker {
/***/ BAD_TIMEZONE,
/***/ MISSING_EMAIL,
/***/ MISSING_SPACE_BEFORE_DATE,
+ /** @since 5.2 */ GITMODULES_BLOB,
+ /** @since 5.2 */ GITMODULES_LARGE,
+ /** @since 5.2 */ GITMODULES_NAME,
+ /** @since 5.2 */ GITMODULES_PARSE,
+ /** @since 5.2 */ GITMODULES_PATH,
+ /** @since 5.2 */ GITMODULES_SYMLINK,
+ /** @since 5.2 */ GITMODULES_URL,
/***/ UNKNOWN_TYPE,
// These are unique to JGit.
@@ -1251,4 +1259,19 @@ public class ObjectChecker {
public List<GitmoduleEntry> getGitsubmodules() {
return gitsubmodules;
}
+
+ /**
+ * Reset the invocation-specific state from this instance. Specifically this
+ * clears the list of .gitmodules files encountered (see
+ * {@link #getGitsubmodules()})
+ *
+ * Configurations like errors to filter, skip lists or the specified O.S.
+ * (set via {@link #setSafeForMacOS(boolean)} or
+ * {@link #setSafeForWindows(boolean)}) are NOT cleared.
+ *
+ * @since 5.2
+ */
+ public void reset() {
+ gitsubmodules.clear();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
index d81ee45c9e..9d8d71a0be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java
@@ -49,7 +49,7 @@ package org.eclipse.jgit.lib;
*/
public interface ProgressMonitor {
/** Constant indicating the total work units cannot be predicted. */
- public static final int UNKNOWN = 0;
+ int UNKNOWN = 0;
/**
* Advise the monitor of the total number of subtasks.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
index b000558944..faabbf892f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -61,7 +61,7 @@ import org.eclipse.jgit.annotations.Nullable;
*/
public interface Ref {
/** Location where a {@link Ref} is stored. */
- public static enum Storage {
+ enum Storage {
/**
* The ref does not exist yet, updating it may create it.
* <p>
@@ -131,7 +131,7 @@ public interface Ref {
* @return name of this ref.
*/
@NonNull
- public String getName();
+ String getName();
/**
* Test if this reference is a symbolic reference.
@@ -144,7 +144,7 @@ public interface Ref {
* @return true if this is a symbolic reference; false if this reference
* contains its own ObjectId.
*/
- public abstract boolean isSymbolic();
+ boolean isSymbolic();
/**
* Traverse target references until {@link #isSymbolic()} is false.
@@ -163,7 +163,7 @@ public interface Ref {
* @return the reference that actually stores the ObjectId value.
*/
@NonNull
- public abstract Ref getLeaf();
+ Ref getLeaf();
/**
* Get the reference this reference points to, or {@code this}.
@@ -178,7 +178,7 @@ public interface Ref {
* @return the target reference, or {@code this}.
*/
@NonNull
- public abstract Ref getTarget();
+ Ref getTarget();
/**
* Cached value of this ref.
@@ -188,7 +188,7 @@ public interface Ref {
* symbolic ref pointing to an unborn branch.
*/
@Nullable
- public abstract ObjectId getObjectId();
+ ObjectId getObjectId();
/**
* Cached value of <code>ref^{}</code> (the ref peeled to commit).
@@ -198,14 +198,14 @@ public interface Ref {
* does not refer to an annotated tag.
*/
@Nullable
- public abstract ObjectId getPeeledObjectId();
+ ObjectId getPeeledObjectId();
/**
* Whether the Ref represents a peeled tag.
*
* @return whether the Ref represents a peeled tag.
*/
- public abstract boolean isPeeled();
+ boolean isPeeled();
/**
* How was this ref obtained?
@@ -216,5 +216,5 @@ public interface Ref {
* @return type of ref.
*/
@NonNull
- public abstract Storage getStorage();
+ Storage getStorage();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index 3170787dd9..68929b4220 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -415,6 +415,31 @@ public abstract class RefDatabase {
}
/**
+ * Returns refs whose names start with one of the given prefixes.
+ * <p>
+ * The default implementation uses {@link #getRefsByPrefix(String)}.
+ * Implementors of {@link RefDatabase} should override this method directly
+ * if a better implementation is possible.
+ *
+ * @param prefixes
+ * strings that names of refs should start with.
+ * @return immutable list of refs whose names start with one of
+ * {@code prefixes}. Refs can be unsorted and may contain duplicates
+ * if the prefixes overlap.
+ * @throws java.io.IOException
+ * the reference space cannot be accessed.
+ * @since 5.2
+ */
+ @NonNull
+ public List<Ref> getRefsByPrefix(String... prefixes) throws IOException {
+ List<Ref> result = new ArrayList<>();
+ for (String prefix : prefixes) {
+ result.addAll(getRefsByPrefix(prefix));
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
* Check if any refs exist in the ref database.
* <p>
* This uses the same definition of refs as {@link #getRefs()}. In
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
index 51f2ea0ab7..824bbc4201 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
@@ -57,7 +57,7 @@ public interface ReflogEntry {
*
* @since 4.9
*/
- public static final String PREFIX_CREATED = "created"; //$NON-NLS-1$
+ String PREFIX_CREATED = "created"; //$NON-NLS-1$
/**
* Prefix used in reflog messages when the ref was updated with a fast
@@ -69,7 +69,7 @@ public interface ReflogEntry {
*
* @since 4.9
*/
- public static final String PREFIX_FAST_FORWARD = "fast-forward"; //$NON-NLS-1$
+ String PREFIX_FAST_FORWARD = "fast-forward"; //$NON-NLS-1$
/**
* Prefix used in reflog messages when the ref was force updated.
@@ -80,35 +80,35 @@ public interface ReflogEntry {
*
* @since 4.9
*/
- public static final String PREFIX_FORCED_UPDATE = "forced-update"; //$NON-NLS-1$
+ String PREFIX_FORCED_UPDATE = "forced-update"; //$NON-NLS-1$
/**
* Get the commit id before the change
*
* @return the commit id before the change
*/
- public abstract ObjectId getOldId();
+ ObjectId getOldId();
/**
* Get the commit id after the change
*
* @return the commit id after the change
*/
- public abstract ObjectId getNewId();
+ ObjectId getNewId();
/**
* Get user performing the change
*
* @return user performing the change
*/
- public abstract PersonIdent getWho();
+ PersonIdent getWho();
/**
* Get textual description of the change
*
* @return textual description of the change
*/
- public abstract String getComment();
+ String getComment();
/**
* Parse checkout
@@ -117,6 +117,6 @@ public interface ReflogEntry {
* information about a branch switch, or null if the entry is not a
* checkout
*/
- public abstract CheckoutEntry parseCheckout();
+ CheckoutEntry parseCheckout();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
index f97b07e08c..4f104d2d7c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
@@ -59,7 +59,7 @@ public interface ReflogReader {
* @return the latest reflog entry, or null if no log
* @throws java.io.IOException
*/
- public abstract ReflogEntry getLastEntry() throws IOException;
+ ReflogEntry getLastEntry() throws IOException;
/**
* Get all reflog entries in reverse order
@@ -67,7 +67,7 @@ public interface ReflogReader {
* @return all reflog entries in reverse order
* @throws java.io.IOException
*/
- public abstract List<ReflogEntry> getReverseEntries() throws IOException;
+ List<ReflogEntry> getReverseEntries() throws IOException;
/**
* Get specific entry in the reflog relative to the last entry which is
@@ -77,7 +77,7 @@ public interface ReflogReader {
* @return reflog entry or null if not found
* @throws java.io.IOException
*/
- public abstract ReflogEntry getReverseEntry(int number) throws IOException;
+ ReflogEntry getReverseEntry(int number) throws IOException;
/**
* Get all reflog entries in reverse order
@@ -87,7 +87,5 @@ public interface ReflogReader {
* @return all reflog entries in reverse order
* @throws java.io.IOException
*/
- public abstract List<ReflogEntry> getReverseEntries(int max)
- throws IOException;
-
+ List<ReflogEntry> getReverseEntries(int max) throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 2a2699f906..77d268a3bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -1981,7 +1981,6 @@ public abstract class Repository implements AutoCloseable {
* empty
* @throws IOException
*/
- @Nullable
private byte[] readGitDirectoryFile(String filename) throws IOException {
File file = new File(getDirectory(), filename);
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
index 036917e62a..479670873d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
@@ -45,6 +45,7 @@ package org.eclipse.jgit.merge;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@@ -63,7 +64,7 @@ public class MergeFormatter {
* that are LF-separated lines.
*
* @param out
- * the outputstream where to write the textual presentation
+ * the output stream where to write the textual presentation
* @param res
* the merge result which should be presented
* @param seqName
@@ -72,13 +73,44 @@ public class MergeFormatter {
* " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
* names for the sequences are given in this list
* @param charsetName
- * the name of the characterSet used when writing conflict
+ * the name of the character set used when writing conflict
* metadata
* @throws java.io.IOException
+ * @deprecated Use
+ * {@link #formatMerge(OutputStream, MergeResult, List, Charset)}
+ * instead.
*/
+ @Deprecated
public void formatMerge(OutputStream out, MergeResult<RawText> res,
List<String> seqName, String charsetName) throws IOException {
- new MergeFormatterPass(out, res, seqName, charsetName).formatMerge();
+ formatMerge(out, res, seqName, Charset.forName(charsetName));
+ }
+
+ /**
+ * Formats the results of a merge of {@link org.eclipse.jgit.diff.RawText}
+ * objects in a Git conformant way. This method also assumes that the
+ * {@link org.eclipse.jgit.diff.RawText} objects being merged are line
+ * oriented files which use LF as delimiter. This method will also use LF to
+ * separate chunks and conflict metadata, therefore it fits only to texts
+ * that are LF-separated lines.
+ *
+ * @param out
+ * the output stream where to write the textual presentation
+ * @param res
+ * the merge result which should be presented
+ * @param seqName
+ * When a conflict is reported each conflicting range will get a
+ * name. This name is following the "&lt;&lt;&lt;&lt;&lt;&lt;&lt;
+ * " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
+ * names for the sequences are given in this list
+ * @param charset
+ * the character set used when writing conflict metadata
+ * @throws java.io.IOException
+ * @since 5.2
+ */
+ public void formatMerge(OutputStream out, MergeResult<RawText> res,
+ List<String> seqName, Charset charset) throws IOException {
+ new MergeFormatterPass(out, res, seqName, charset).formatMerge();
}
/**
@@ -100,17 +132,51 @@ public class MergeFormatter {
* @param theirsName
* the name ranges from theirs should get
* @param charsetName
- * the name of the characterSet used when writing conflict
+ * the name of the character set used when writing conflict
* metadata
* @throws java.io.IOException
+ * @deprecated use
+ * {@link #formatMerge(OutputStream, MergeResult, String, String, String, Charset)}
+ * instead.
*/
- @SuppressWarnings("unchecked")
+ @Deprecated
public void formatMerge(OutputStream out, MergeResult res, String baseName,
String oursName, String theirsName, String charsetName) throws IOException {
+ formatMerge(out, res, baseName, oursName, theirsName,
+ Charset.forName(charsetName));
+ }
+
+ /**
+ * Formats the results of a merge of exactly two
+ * {@link org.eclipse.jgit.diff.RawText} objects in a Git conformant way.
+ * This convenience method accepts the names for the three sequences (base
+ * and the two merged sequences) as explicit parameters and doesn't require
+ * the caller to specify a List
+ *
+ * @param out
+ * the {@link java.io.OutputStream} where to write the textual
+ * presentation
+ * @param res
+ * the merge result which should be presented
+ * @param baseName
+ * the name ranges from the base should get
+ * @param oursName
+ * the name ranges from ours should get
+ * @param theirsName
+ * the name ranges from theirs should get
+ * @param charset
+ * the character set used when writing conflict metadata
+ * @throws java.io.IOException
+ * @since 5.2
+ */
+ @SuppressWarnings("unchecked")
+ public void formatMerge(OutputStream out, MergeResult res, String baseName,
+ String oursName, String theirsName, Charset charset)
+ throws IOException {
List<String> names = new ArrayList<>(3);
names.add(baseName);
names.add(oursName);
names.add(theirsName);
- formatMerge(out, res, names, charsetName);
+ formatMerge(out, res, names, charset);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
index 060f06884a..e1a8d3110e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
@@ -46,6 +46,7 @@ package org.eclipse.jgit.merge;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.charset.Charset;
import java.util.List;
import org.eclipse.jgit.diff.RawText;
@@ -59,19 +60,33 @@ class MergeFormatterPass {
private final List<String> seqName;
- private final String charsetName;
+ private final Charset charset;
private final boolean threeWayMerge;
private String lastConflictingName; // is set to non-null whenever we are in
// a conflict
- MergeFormatterPass(OutputStream out, MergeResult<RawText> res, List<String> seqName,
- String charsetName) {
+ /**
+ * @param out
+ * the {@link java.io.OutputStream} where to write the textual
+ * presentation
+ * @param res
+ * the merge result which should be presented
+ * @param seqName
+ * When a conflict is reported each conflicting range will get a
+ * name. This name is following the "&lt;&lt;&lt;&lt;&lt;&lt;&lt;
+ * " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
+ * names for the sequences are given in this list
+ * @param charset
+ * the character set used when writing conflict metadata
+ */
+ MergeFormatterPass(OutputStream out, MergeResult<RawText> res,
+ List<String> seqName, Charset charset) {
this.out = new EolAwareOutputStream(out);
this.res = res;
this.seqName = seqName;
- this.charsetName = charsetName;
+ this.charset = charset;
this.threeWayMerge = (res.getSequences().size() == 3);
}
@@ -133,7 +148,7 @@ class MergeFormatterPass {
private void writeln(String s) throws IOException {
out.beginln();
- out.write((s + "\n").getBytes(charsetName)); //$NON-NLS-1$
+ out.write((s + "\n").getBytes(charset)); //$NON-NLS-1$
}
private void writeLine(RawText seq, int i) throws IOException {
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 e37f207315..909f3b15d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -1029,7 +1029,7 @@ public class ResolveMerger extends ThreeWayMerger {
db != null ? nonNullRepo().getDirectory() : null, inCoreLimit);
try {
new MergeFormatter().formatMerge(buf, result,
- Arrays.asList(commitNames), UTF_8.name());
+ Arrays.asList(commitNames), UTF_8);
buf.close();
} catch (IOException e) {
buf.destroy();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java
index d263184622..98654f14c2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AsyncRevObjectQueue.java
@@ -66,5 +66,5 @@ public interface AsyncRevObjectQueue extends AsyncOperation {
* @throws java.io.IOException
* the object store cannot be accessed.
*/
- public RevObject next() throws MissingObjectException, IOException;
+ RevObject next() throws MissingObjectException, IOException;
}
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 eaec305b47..5154920393 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
@@ -48,6 +48,7 @@ import java.io.IOException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
/**
* Only produce commits which are below a specified depth.
@@ -59,6 +60,8 @@ class DepthGenerator extends Generator {
private final int depth;
+ private final int deepenSince;
+
private final RevWalk walk;
/**
@@ -79,6 +82,11 @@ class DepthGenerator extends Generator {
private final RevFlag REINTERESTING;
/**
+ * Commits reachable from commits that the client specified using --shallow-exclude.
+ */
+ private final RevFlag DEEPEN_NOT;
+
+ /**
* @param w
* @param s Parent generator
* @throws MissingObjectException
@@ -91,8 +99,10 @@ class DepthGenerator extends Generator {
walk = (RevWalk)w;
this.depth = w.getDepth();
+ this.deepenSince = w.getDeepenSince();
this.UNSHALLOW = w.getUnshallowFlag();
this.REINTERESTING = w.getReinterestingFlag();
+ this.DEEPEN_NOT = w.getDeepenNotFlag();
s.shareFreeList(pending);
@@ -105,6 +115,37 @@ class DepthGenerator extends Generator {
if (((DepthWalk.Commit) c).getDepth() == 0)
pending.add(c);
}
+
+ // Mark DEEPEN_NOT on all deepen-not commits and their ancestors.
+ // TODO(jonathantanmy): This implementation is somewhat
+ // inefficient in that any "deepen-not <ref>" in the request
+ // results in all commits reachable from that ref being parsed
+ // and marked, even if the commit topology is such that it is
+ // not necessary.
+ for (ObjectId oid : w.getDeepenNots()) {
+ RevCommit c;
+ try {
+ c = walk.parseCommit(oid);
+ } catch (IncorrectObjectTypeException notCommit) {
+ // The C Git implementation silently tolerates
+ // non-commits, so do the same here.
+ continue;
+ }
+
+ FIFORevQueue queue = new FIFORevQueue();
+ queue.add(c);
+ while ((c = queue.next()) != null) {
+ if (c.has(DEEPEN_NOT)) {
+ continue;
+ }
+
+ walk.parseHeaders(c);
+ c.add(DEEPEN_NOT);
+ for (RevCommit p : c.getParents()) {
+ queue.add(p);
+ }
+ }
+ }
}
@Override
@@ -132,6 +173,14 @@ class DepthGenerator extends Generator {
if ((c.flags & RevWalk.PARSED) == 0)
c.parseHeaders(walk);
+ if (c.getCommitTime() < deepenSince) {
+ continue;
+ }
+
+ if (c.has(DEEPEN_NOT)) {
+ continue;
+ }
+
int newDepth = c.depth + 1;
for (RevCommit p : c.parents) {
@@ -142,12 +191,29 @@ class DepthGenerator extends Generator {
// this depth is guaranteed to be the smallest value that
// any path could produce.
if (dp.depth == -1) {
+ boolean failsDeepenSince = false;
+ if (deepenSince != 0) {
+ if ((p.flags & RevWalk.PARSED) == 0) {
+ p.parseHeaders(walk);
+ }
+ failsDeepenSince =
+ p.getCommitTime() < deepenSince;
+ }
+
dp.depth = newDepth;
- // If the parent is not too deep, add it to the queue
- // so that we can produce it later
- if (newDepth <= depth)
+ // If the parent is not too deep and was not excluded, add
+ // it to the queue so that we can produce it later
+ if (newDepth <= depth && !failsDeepenSince &&
+ !p.has(DEEPEN_NOT)) {
pending.add(p);
+ } else {
+ dp.makesChildBoundary = true;
+ }
+ }
+
+ if (dp.makesChildBoundary) {
+ c.isBoundary = true;
}
// If the current commit has become unshallowed, everything
@@ -160,8 +226,7 @@ class DepthGenerator extends Generator {
}
}
- // Produce all commits less than the depth cutoff
- boolean produce = c.depth <= depth;
+ boolean produce = true;
// Unshallow commits are uninteresting, but still need to be sent
// up to the PackWriter so that it will exclude objects correctly.
@@ -169,6 +234,10 @@ class DepthGenerator extends Generator {
if ((c.flags & RevWalk.UNINTERESTING) != 0 && !c.has(UNSHALLOW))
produce = false;
+ if (c.getCommitTime() < deepenSince) {
+ produce = false;
+ }
+
if (produce)
return c;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
index 06a5272b98..0201f0b602 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
@@ -45,10 +45,14 @@
package org.eclipse.jgit.revwalk;
import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
@@ -61,7 +65,24 @@ public interface DepthWalk {
*
* @return Depth to filter to.
*/
- public int getDepth();
+ int getDepth();
+
+ /**
+ * @return the deepen-since value; if not 0, this walk only returns commits
+ * whose commit time is at or after this limit
+ * @since 5.2
+ */
+ default int getDeepenSince() {
+ return 0;
+ }
+
+ /**
+ * @return the objects specified by the client using --shallow-exclude
+ * @since 5.2
+ */
+ default List<ObjectId> getDeepenNots() {
+ return Collections.emptyList();
+ }
/** @return flag marking commits that should become unshallow. */
/**
@@ -69,26 +90,49 @@ public interface DepthWalk {
*
* @return flag marking commits that should become unshallow.
*/
- public RevFlag getUnshallowFlag();
+ RevFlag getUnshallowFlag();
/**
* Get flag marking commits that are interesting again.
*
* @return flag marking commits that are interesting again.
*/
- public RevFlag getReinterestingFlag();
+ RevFlag getReinterestingFlag();
+
+ /**
+ * @return flag marking commits that are to be excluded because of --shallow-exclude
+ * @since 5.2
+ */
+ RevFlag getDeepenNotFlag();
/** RevCommit with a depth (in commits) from a root. */
public static class Commit extends RevCommit {
/** Depth of this commit in the graph, via shortest path. */
int depth;
+ boolean isBoundary;
+
+ /**
+ * True if this commit was excluded due to a shallow fetch
+ * setting. All its children are thus boundary commits.
+ */
+ boolean makesChildBoundary;
+
/** @return depth of this commit, as found by the shortest path. */
public int getDepth() {
return depth;
}
/**
+ * @return true if at least one of this commit's parents was excluded
+ * due to a shallow fetch setting, false otherwise
+ * @since 5.2
+ */
+ public boolean isBoundary() {
+ return isBoundary;
+ }
+
+ /**
* Initialize a new commit.
*
* @param id
@@ -104,10 +148,16 @@ public interface DepthWalk {
public class RevWalk extends org.eclipse.jgit.revwalk.RevWalk implements DepthWalk {
private final int depth;
+ private int deepenSince;
+
+ private List<ObjectId> deepenNots;
+
private final RevFlag UNSHALLOW;
private final RevFlag REINTERESTING;
+ private final RevFlag DEEPEN_NOT;
+
/**
* @param repo Repository to walk
* @param depth Maximum depth to return
@@ -116,8 +166,10 @@ public interface DepthWalk {
super(repo);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
@@ -128,8 +180,10 @@ public interface DepthWalk {
super(or);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
@@ -159,6 +213,39 @@ public interface DepthWalk {
}
@Override
+ public int getDeepenSince() {
+ return deepenSince;
+ }
+
+ /**
+ * Sets the deepen-since value.
+ *
+ * @param limit
+ * new deepen-since value
+ * @since 5.2
+ */
+ public void setDeepenSince(int limit) {
+ deepenSince = limit;
+ }
+
+ @Override
+ public List<ObjectId> getDeepenNots() {
+ return deepenNots;
+ }
+
+ /**
+ * Mark objects that the client specified using
+ * --shallow-exclude. Objects that are not commits have no
+ * effect.
+ *
+ * @param deepenNots specified objects
+ * @since 5.2
+ */
+ public void setDeepenNots(List<ObjectId> deepenNots) {
+ this.deepenNots = Objects.requireNonNull(deepenNots);
+ }
+
+ @Override
public RevFlag getUnshallowFlag() {
return UNSHALLOW;
}
@@ -168,12 +255,19 @@ public interface DepthWalk {
return REINTERESTING;
}
+ @Override
+ public RevFlag getDeepenNotFlag() {
+ return DEEPEN_NOT;
+ }
+
/**
* @since 4.5
*/
@Override
public ObjectWalk toObjectWalkWithSameObjects() {
ObjectWalk ow = new ObjectWalk(reader, depth);
+ ow.deepenSince = deepenSince;
+ ow.deepenNots = deepenNots;
ow.objects = objects;
ow.freeFlags = freeFlags;
return ow;
@@ -184,10 +278,16 @@ public interface DepthWalk {
public class ObjectWalk extends org.eclipse.jgit.revwalk.ObjectWalk implements DepthWalk {
private final int depth;
+ private int deepenSince;
+
+ private List<ObjectId> deepenNots;
+
private final RevFlag UNSHALLOW;
private final RevFlag REINTERESTING;
+ private final RevFlag DEEPEN_NOT;
+
/**
* @param repo Repository to walk
* @param depth Maximum depth to return
@@ -196,8 +296,10 @@ public interface DepthWalk {
super(repo);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
@@ -208,8 +310,10 @@ public interface DepthWalk {
super(or);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
@@ -263,6 +367,16 @@ public interface DepthWalk {
}
@Override
+ public int getDeepenSince() {
+ return deepenSince;
+ }
+
+ @Override
+ public List<ObjectId> getDeepenNots() {
+ return deepenNots;
+ }
+
+ @Override
public RevFlag getUnshallowFlag() {
return UNSHALLOW;
}
@@ -271,5 +385,10 @@ public interface DepthWalk {
public RevFlag getReinterestingFlag() {
return REINTERESTING;
}
+
+ @Override
+ public RevFlag getDeepenNotFlag() {
+ return DEEPEN_NOT;
+ }
}
}
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 86ecd8eaee..af4ec1f00b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -407,7 +407,7 @@ public class RevCommit extends RevObject {
* @return contents of the gpg signature; null if the commit was not signed.
* @since 5.1
*/
- public final @Nullable byte[] getRawGpgSignature() {
+ public final byte[] getRawGpgSignature() {
final byte[] raw = buffer;
final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'};
final int start = RawParseUtils.headerStart(header, raw, 0);
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 4d555d2178..400ea33c21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -54,6 +54,7 @@ import java.util.Iterator;
import java.util.List;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.LargeObjectException;
@@ -1336,6 +1337,22 @@ 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.
+ * @return next most recent commit; null if traversal is over.
+ */
+ @Nullable
+ private RevCommit nextForIterator() {
+ try {
+ return next();
+ } catch (IOException e) {
+ throw new RevWalkException(e);
+ }
+ }
+
+ /**
* {@inheritDoc}
* <p>
* Returns an Iterator over the commits of this walker.
@@ -1353,16 +1370,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
*/
@Override
public Iterator<RevCommit> iterator() {
- final RevCommit first;
- try {
- first = RevWalk.this.next();
- } catch (MissingObjectException e) {
- throw new RevWalkException(e);
- } catch (IncorrectObjectTypeException e) {
- throw new RevWalkException(e);
- } catch (IOException e) {
- throw new RevWalkException(e);
- }
+ RevCommit first = nextForIterator();
return new Iterator<RevCommit>() {
RevCommit next = first;
@@ -1374,17 +1382,9 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
@Override
public RevCommit next() {
- try {
- final RevCommit r = next;
- next = RevWalk.this.next();
- return r;
- } catch (MissingObjectException e) {
- throw new RevWalkException(e);
- } catch (IncorrectObjectTypeException e) {
- throw new RevWalkException(e);
- } catch (IOException e) {
- throw new RevWalkException(e);
- }
+ RevCommit r = next;
+ next = nextForIterator();
+ return r;
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index e7b0941f2d..bdbd7c9072 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -57,7 +57,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.MessageFormat;
-import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
@@ -283,7 +282,6 @@ public class FileBasedConfig extends StoredConfig {
* @since 4.10
*/
@Override
- @Nullable
protected byte[] readIncludedConfig(String relPath)
throws ConfigInvalidException {
final File file;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
index ed05c733f3..72b4255df9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
@@ -55,7 +55,7 @@ public interface AdvertiseRefsHook {
* {@link UploadPack#setAdvertisedRefs(java.util.Map)} and
* {@link BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}.
*/
- public static final AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() {
+ AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() {
@Override
public void advertiseRefs(UploadPack uploadPack) {
// Do nothing.
@@ -77,7 +77,7 @@ public interface AdvertiseRefsHook {
* @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
* abort; the message will be sent to the user.
*/
- public void advertiseRefs(UploadPack uploadPack)
+ void advertiseRefs(UploadPack uploadPack)
throws ServiceMayNotContinueException;
/**
@@ -90,6 +90,6 @@ public interface AdvertiseRefsHook {
* @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
* abort; the message will be sent to the user.
*/
- public void advertiseRefs(BaseReceivePack receivePack)
+ void advertiseRefs(BaseReceivePack receivePack)
throws ServiceMayNotContinueException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
index f6ec4b90eb..c5661e5083 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -542,7 +542,7 @@ public class AmazonS3 {
}
buf = b.toByteArray();
if (buf.length > 0) {
- err.initCause(new IOException("\n" + new String(buf))); //$NON-NLS-1$
+ err.initCause(new IOException("\n" + new String(buf, UTF_8))); //$NON-NLS-1$
}
}
return err;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index d3419bc201..03763368a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -72,12 +72,14 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.TooLargePackException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.PackLock;
import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Config;
@@ -1528,8 +1530,12 @@ public abstract class BaseReceivePack {
AnyObjectId blobId = entry.getBlobId();
ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB);
- SubmoduleValidator.assertValidGitModulesFile(
- new String(blob.getBytes(), UTF_8));
+ try {
+ SubmoduleValidator.assertValidGitModulesFile(
+ new String(blob.getBytes(), UTF_8));
+ } catch (LargeObjectException | SubmoduleValidationException e) {
+ throw new IOException(e);
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
index d4c514e636..19a1ab0b93 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -68,7 +68,7 @@ public interface Connection extends AutoCloseable {
* modifiable. The collection can be empty if the remote side has no
* refs (it is an empty/newly created repository).
*/
- public Map<String, Ref> getRefsMap();
+ Map<String, Ref> getRefsMap();
/**
* Get the complete list of refs advertised as available for fetching or
@@ -82,7 +82,7 @@ public interface Connection extends AutoCloseable {
* collection can be empty if the remote side has no refs (it is an
* empty/newly created repository).
*/
- public Collection<Ref> getRefs();
+ Collection<Ref> getRefs();
/**
* Get a single advertised ref by name.
@@ -95,7 +95,7 @@ public interface Connection extends AutoCloseable {
* name of the ref to obtain.
* @return the requested ref; null if the remote did not advertise this ref.
*/
- public Ref getRef(String name);
+ Ref getRef(String name);
/**
* {@inheritDoc}
@@ -115,7 +115,7 @@ public interface Connection extends AutoCloseable {
* the signature to prevent them from doing so.
*/
@Override
- public void close();
+ void close();
/**
* Get the additional messages, if any, returned by the remote process.
@@ -132,7 +132,7 @@ public interface Connection extends AutoCloseable {
* newline (LF) character. The empty string is returned if the
* remote produced no additional messages.
*/
- public String getMessages();
+ String getMessages();
/**
* User agent advertised by the remote server.
@@ -141,5 +141,5 @@ public interface Connection extends AutoCloseable {
* server does not advertise this version.
* @since 4.0
*/
- public String getPeerUserAgent();
+ String getPeerUserAgent();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
index f0c45d5fb6..1eb7cbd93a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java
@@ -109,7 +109,7 @@ public interface FetchConnection extends Connection {
* protocol error, or error on remote side, or connection was
* already used for fetch.
*/
- public void fetch(final ProgressMonitor monitor,
+ void fetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have)
throws TransportException;
@@ -151,7 +151,7 @@ public interface FetchConnection extends Connection {
* already used for fetch.
* @since 3.0
*/
- public void fetch(final ProgressMonitor monitor,
+ void fetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have,
OutputStream out) throws TransportException;
@@ -173,7 +173,7 @@ public interface FetchConnection extends Connection {
* @return true if the last fetch call implicitly included tag objects;
* false if tags were not implicitly obtained.
*/
- public boolean didFetchIncludeTags();
+ boolean didFetchIncludeTags();
/**
* Did the last {@link #fetch(ProgressMonitor, Collection, Set)} validate
@@ -196,7 +196,7 @@ public interface FetchConnection extends Connection {
* client side in order to succeed; false if the last fetch assumed
* the remote peer supplied a complete graph.
*/
- public boolean didFetchTestConnectivity();
+ boolean didFetchTestConnectivity();
/**
* Set the lock message used when holding a pack out of garbage collection.
@@ -208,7 +208,7 @@ public interface FetchConnection extends Connection {
*
* @param message message to use when holding a pack in place.
*/
- public void setPackLockMessage(String message);
+ void setPackLockMessage(String message);
/**
* All locks created by the last
@@ -218,5 +218,5 @@ public interface FetchConnection extends Connection {
* fetch. The caller must release these after refs are updated in
* order to safely permit garbage collection.
*/
- public Collection<PackLock> getPackLocks();
+ Collection<PackLock> getPackLocks();
}
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 c43ab18c35..211707e9ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -44,6 +44,7 @@
package org.eclipse.jgit.transport;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
@@ -337,7 +338,7 @@ class FetchProcess {
try {
if (lock.lock()) {
try (Writer w = new OutputStreamWriter(
- lock.getOutputStream())) {
+ lock.getOutputStream(), UTF_8)) {
for (FetchHeadRecord h : fetchHeadUpdates) {
h.write(w);
result.add(h);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
new file mode 100644
index 0000000000..40ba3a3ad2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Common fields between v0/v1/v2 fetch requests.
+ */
+abstract class FetchRequest {
+
+ final Set<ObjectId> wantIds;
+
+ final int depth;
+
+ final Set<ObjectId> clientShallowCommits;
+
+ final long filterBlobLimit;
+
+ final Set<String> clientCapabilities;
+
+ final int deepenSince;
+
+ final List<String> deepenNotRefs;
+
+ @Nullable
+ final String agent;
+
+ /**
+ * Initialize the common fields of a fetch request.
+ *
+ * @param wantIds
+ * list of want ids
+ * @param depth
+ * how deep to go in the tree
+ * @param clientShallowCommits
+ * commits the client has without history
+ * @param filterBlobLimit
+ * to exclude blobs on certain conditions
+ * @param clientCapabilities
+ * capabilities sent in the request
+ * @param deepenNotRefs
+ * Requests that the shallow clone/fetch should be cut at these
+ * specific revisions instead of a depth.
+ * @param deepenSince
+ * Requests that the shallow clone/fetch should be cut at a
+ * specific time, instead of depth
+ * @param agent
+ * agent as reported by the client in the request body
+ */
+ FetchRequest(@NonNull Set<ObjectId> wantIds, int depth,
+ @NonNull Set<ObjectId> clientShallowCommits, long filterBlobLimit,
+ @NonNull Set<String> clientCapabilities, int deepenSince,
+ @NonNull List<String> deepenNotRefs, @Nullable String agent) {
+ this.wantIds = requireNonNull(wantIds);
+ this.depth = depth;
+ this.clientShallowCommits = requireNonNull(clientShallowCommits);
+ this.filterBlobLimit = filterBlobLimit;
+ this.clientCapabilities = requireNonNull(clientCapabilities);
+ this.deepenSince = deepenSince;
+ this.deepenNotRefs = requireNonNull(deepenNotRefs);
+ this.agent = agent;
+ }
+
+ /**
+ * @return object ids in the "want" (and "want-ref") lines of the request
+ */
+ @NonNull
+ Set<ObjectId> getWantIds() {
+ return wantIds;
+ }
+
+ /**
+ * @return the depth set in a "deepen" line. 0 by default.
+ */
+ int getDepth() {
+ return depth;
+ }
+
+ /**
+ * Shallow commits the client already has.
+ *
+ * These are sent by the client in "shallow" request lines.
+ *
+ * @return set of commits the client has declared as shallow.
+ */
+ @NonNull
+ Set<ObjectId> getClientShallowCommits() {
+ return clientShallowCommits;
+ }
+
+ /**
+ * @return the blob limit set in a "filter" line (-1 if not set)
+ */
+ long getFilterBlobLimit() {
+ return filterBlobLimit;
+ }
+
+ /**
+ * Capabilities that the client wants enabled from the server.
+ *
+ * Capabilities are options that tune the expected response from the server,
+ * like "thin-pack", "no-progress" or "ofs-delta". This list should be a
+ * subset of the capabilities announced by the server in its first response.
+ *
+ * These options are listed and well-defined in the git protocol
+ * specification.
+ *
+ * The agent capability is not included in this set. It can be retrieved via
+ * {@link #getAgent()}.
+ *
+ * @return capabilities sent by the client (excluding the "agent"
+ * capability)
+ */
+ @NonNull
+ Set<String> getClientCapabilities() {
+ return clientCapabilities;
+ }
+
+ /**
+ * The value in a "deepen-since" line in the request, indicating the
+ * timestamp where to stop fetching/cloning.
+ *
+ * @return timestamp in seconds since the epoch, where to stop the shallow
+ * fetch/clone. Defaults to 0 if not set in the request.
+ */
+ int getDeepenSince() {
+ return deepenSince;
+ }
+
+ /**
+ * @return refs received in "deepen-not" lines.
+ */
+ @NonNull
+ List<String> getDeepenNotRefs() {
+ return deepenNotRefs;
+ }
+
+ /**
+ * @return string identifying the agent (as sent in the request body by the
+ * client)
+ */
+ @Nullable
+ String getAgent() {
+ return agent;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
new file mode 100644
index 0000000000..05f4a8155f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Fetch request in the V0/V1 protocol.
+ */
+final class FetchV0Request extends FetchRequest {
+
+ FetchV0Request(@NonNull Set<ObjectId> wantIds, int depth,
+ @NonNull Set<ObjectId> clientShallowCommits, long filterBlobLimit,
+ @NonNull Set<String> clientCapabilities, @Nullable String agent) {
+ super(wantIds, depth, clientShallowCommits, filterBlobLimit,
+ clientCapabilities, 0, Collections.emptyList(), agent);
+ }
+
+ static final class Builder {
+
+ int depth;
+
+ final Set<ObjectId> wantIds = new HashSet<>();
+
+ final Set<ObjectId> clientShallowCommits = new HashSet<>();
+
+ long filterBlobLimit = -1;
+
+ final Set<String> clientCaps = new HashSet<>();
+
+ String agent;
+
+ /**
+ * @param objectId
+ * object id received in a "want" line
+ * @return this builder
+ */
+ Builder addWantId(ObjectId objectId) {
+ wantIds.add(objectId);
+ return this;
+ }
+
+ /**
+ * @param d
+ * depth set in a "deepen" line
+ * @return this builder
+ */
+ Builder setDepth(int d) {
+ depth = d;
+ return this;
+ }
+
+ /**
+ * @param shallowOid
+ * object id received in a "shallow" line
+ * @return this builder
+ */
+ Builder addClientShallowCommit(ObjectId shallowOid) {
+ clientShallowCommits.add(shallowOid);
+ return this;
+ }
+
+ /**
+ * @param clientCapabilities
+ * client capabilities sent by the client in the first want
+ * line of the request
+ * @return this builder
+ */
+ Builder addClientCapabilities(Collection<String> clientCapabilities) {
+ clientCaps.addAll(clientCapabilities);
+ return this;
+ }
+
+ /**
+ * @param clientAgent
+ * agent line sent by the client in the request body
+ * @return this builder
+ */
+ Builder setAgent(String clientAgent) {
+ agent = clientAgent;
+ return this;
+ }
+
+ /**
+ * @param filterBlobLim
+ * blob limit set in a "filter" line
+ * @return this builder
+ */
+ Builder setFilterBlobLimit(long filterBlobLim) {
+ filterBlobLimit = filterBlobLim;
+ return this;
+ }
+
+ FetchV0Request build() {
+ return new FetchV0Request(wantIds, depth, clientShallowCommits,
+ filterBlobLimit, clientCaps, 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 34f3484951..ac6361cdeb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
@@ -42,70 +42,62 @@
*/
package org.eclipse.jgit.transport;
+import static java.util.Objects.requireNonNull;
+
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.ObjectId;
/**
- * fetch protocol v2 request.
+ * Fetch request from git protocol v2.
*
* <p>
* This is used as an input to {@link ProtocolV2Hook}.
*
* @since 5.1
*/
-public final class FetchV2Request {
+public final class FetchV2Request extends FetchRequest {
private final List<ObjectId> peerHas;
private final List<String> wantedRefs;
- private final Set<ObjectId> wantsIds;
-
- private final Set<ObjectId> clientShallowCommits;
-
- private final int deepenSince;
-
- private final List<String> deepenNotRefs;
-
- private final int depth;
-
- private final long filterBlobLimit;
-
- private final Set<String> options;
-
private final boolean doneReceived;
- private FetchV2Request(List<ObjectId> peerHas,
- List<String> wantedRefs, Set<ObjectId> wantsIds,
- Set<ObjectId> clientShallowCommits, int deepenSince,
- List<String> deepenNotRefs, int depth, long filterBlobLimit,
- boolean doneReceived, Set<String> options) {
- this.peerHas = peerHas;
- this.wantedRefs = wantedRefs;
- this.wantsIds = wantsIds;
- this.clientShallowCommits = clientShallowCommits;
- this.deepenSince = deepenSince;
- this.deepenNotRefs = deepenNotRefs;
- this.depth = depth;
- this.filterBlobLimit = filterBlobLimit;
+ @NonNull
+ private final List<String> serverOptions;
+
+ FetchV2Request(@NonNull List<ObjectId> peerHas,
+ @NonNull List<String> wantedRefs,
+ @NonNull Set<ObjectId> wantIds,
+ @NonNull Set<ObjectId> clientShallowCommits, int deepenSince,
+ @NonNull List<String> deepenNotRefs, int depth,
+ long filterBlobLimit,
+ boolean doneReceived, @NonNull Set<String> clientCapabilities,
+ @Nullable String agent, @NonNull List<String> serverOptions) {
+ super(wantIds, depth, clientShallowCommits, filterBlobLimit,
+ clientCapabilities, deepenSince, deepenNotRefs, agent);
+ this.peerHas = requireNonNull(peerHas);
+ this.wantedRefs = requireNonNull(wantedRefs);
this.doneReceived = doneReceived;
- this.options = options;
+ this.serverOptions = requireNonNull(serverOptions);
}
/**
- * @return object ids in the "have" lines of the request
+ * @return object ids received in the "have" lines
*/
@NonNull
List<ObjectId> getPeerHas() {
- return this.peerHas;
+ return peerHas;
}
/**
- * @return list of references in the "want-ref" lines of the request
+ * @return list of references received in "want-ref" lines
*/
@NonNull
List<String> getWantedRefs() {
@@ -113,59 +105,6 @@ public final class FetchV2Request {
}
/**
- * @return object ids in the "want" (but not "want-ref") lines of the request
- */
- @NonNull
- Set<ObjectId> getWantsIds() {
- return wantsIds;
- }
-
- /**
- * Shallow commits the client already has.
- *
- * These are sent by the client in "shallow" request lines.
- *
- * @return set of commits the client has declared as shallow.
- */
- @NonNull
- Set<ObjectId> getClientShallowCommits() {
- return clientShallowCommits;
- }
-
- /**
- * The value in a "deepen-since" line in the request, indicating the
- * timestamp where to stop fetching/cloning.
- *
- * @return timestamp in seconds since the epoch, where to stop the shallow
- * fetch/clone. Defaults to 0 if not set in the request.
- */
- int getDeepenSince() {
- return deepenSince;
- }
-
- /**
- * @return the refs in "deepen-not" lines in the request.
- */
- @NonNull
- List<String> getDeepenNotRefs() {
- return deepenNotRefs;
- }
-
- /**
- * @return the depth set in a "deepen" line. 0 by default.
- */
- int getDepth() {
- return depth;
- }
-
- /**
- * @return the blob limit set in a "filter" line (-1 if not set)
- */
- long getFilterBlobLimit() {
- return filterBlobLimit;
- }
-
- /**
* @return true if the request had a "done" line
*/
boolean wasDoneReceived() {
@@ -173,17 +112,16 @@ public final class FetchV2Request {
}
/**
- * Options that tune the expected response from the server, like
- * "thin-pack", "no-progress" or "ofs-delta"
+ * Options received in server-option lines. The caller can choose to act on
+ * these in an application-specific way
*
- * These are options listed and well-defined in the git protocol
- * specification
+ * @return Immutable list of server options received in the request
*
- * @return options found in the request lines
+ * @since 5.2
*/
@NonNull
- Set<String> getOptions() {
- return options;
+ public List<String> getServerOptions() {
+ return serverOptions;
}
/** @return A builder of {@link FetchV2Request}. */
@@ -191,20 +129,19 @@ public final class FetchV2Request {
return new Builder();
}
-
/** A builder for {@link FetchV2Request}. */
static final class Builder {
- List<ObjectId> peerHas = new ArrayList<>();
+ final List<ObjectId> peerHas = new ArrayList<>();
- List<String> wantedRefs = new ArrayList<>();
+ final List<String> wantedRefs = new ArrayList<>();
- Set<ObjectId> wantsIds = new HashSet<>();
+ final Set<ObjectId> wantIds = new HashSet<>();
- Set<ObjectId> clientShallowCommits = new HashSet<>();
+ final Set<ObjectId> clientShallowCommits = new HashSet<>();
- List<String> deepenNotRefs = new ArrayList<>();
+ final List<String> deepenNotRefs = new ArrayList<>();
- Set<String> options = new HashSet<>();
+ final Set<String> clientCapabilities = new HashSet<>();
int depth;
@@ -214,13 +151,18 @@ public final class FetchV2Request {
boolean doneReceived;
+ @Nullable
+ String agent;
+
+ final List<String> serverOptions = new ArrayList<>();
+
private Builder() {
}
/**
* @param objectId
- * from a "have" line in a fetch request
- * @return the builder
+ * object id received in a "have" line
+ * @return this builder
*/
Builder addPeerHas(ObjectId objectId) {
peerHas.add(objectId);
@@ -228,11 +170,11 @@ public final class FetchV2Request {
}
/**
- * From a "want-ref" line in a fetch request
+ * Ref received in "want-ref" line and the object-id it refers to
*
* @param refName
* reference name
- * @return the builder
+ * @return this builder
*/
Builder addWantedRef(String refName) {
wantedRefs.add(refName);
@@ -240,42 +182,42 @@ public final class FetchV2Request {
}
/**
- * @param option
- * fetch request lines acting as options
- * @return the builder
+ * @param clientCapability
+ * capability line sent by the client
+ * @return this builder
*/
- Builder addOption(String option) {
- options.add(option);
+ Builder addClientCapability(String clientCapability) {
+ clientCapabilities.add(clientCapability);
return this;
}
/**
- * @param objectId
- * from a "want" line in a fetch request
- * @return the builder
+ * @param wantId
+ * object id received in a "want" line
+ * @return this builder
*/
- Builder addWantsId(ObjectId objectId) {
- wantsIds.add(objectId);
+ Builder addWantId(ObjectId wantId) {
+ wantIds.add(wantId);
return this;
}
/**
* @param shallowOid
- * from a "shallow" line in the fetch request
- * @return the builder
+ * object id received in a "shallow" line
+ * @return this builder
*/
Builder addClientShallowCommit(ObjectId shallowOid) {
- this.clientShallowCommits.add(shallowOid);
+ clientShallowCommits.add(shallowOid);
return this;
}
/**
* @param d
- * from a "deepen" line in the fetch request
- * @return the builder
+ * Depth received in a "deepen" line
+ * @return this builder
*/
Builder setDepth(int d) {
- this.depth = d;
+ depth = d;
return this;
}
@@ -284,32 +226,34 @@ public final class FetchV2Request {
* 0 if not set.
*/
int getDepth() {
- return this.depth;
+ return depth;
}
/**
- * @return if there has been any "deepen not" line in the request
+ * @return true if there has been at least one "deepen not" line in the
+ * request so far
*/
boolean hasDeepenNotRefs() {
return !deepenNotRefs.isEmpty();
}
/**
- * @param deepenNotRef reference in a "deepen not" line
- * @return the builder
+ * @param deepenNotRef
+ * reference received in a "deepen not" line
+ * @return this builder
*/
Builder addDeepenNotRef(String deepenNotRef) {
- this.deepenNotRefs.add(deepenNotRef);
+ deepenNotRefs.add(deepenNotRef);
return this;
}
/**
* @param value
* Unix timestamp received in a "deepen since" line
- * @return the builder
+ * @return this builder
*/
Builder setDeepenSince(int value) {
- this.deepenSince = value;
+ deepenSince = value;
return this;
}
@@ -318,35 +262,66 @@ public final class FetchV2Request {
* by default.
*/
int getDeepenSince() {
- return this.deepenSince;
+ return deepenSince;
}
/**
- * @param filterBlobLimit
+ * @param filterBlobLim
* set in a "filter" line
- * @return the builder
+ * @return this builder
*/
- Builder setFilterBlobLimit(long filterBlobLimit) {
- this.filterBlobLimit = filterBlobLimit;
+ Builder setFilterBlobLimit(long filterBlobLim) {
+ filterBlobLimit = filterBlobLim;
return this;
}
/**
* Mark that the "done" line has been received.
*
- * @return the builder
+ * @return this builder
*/
Builder setDoneReceived() {
- this.doneReceived = true;
+ doneReceived = true;
return this;
}
+
+ /**
+ * Value of an agent line received after the command and before the
+ * arguments. E.g. "agent=a.b.c/1.0" should set "a.b.c/1.0".
+ *
+ * @param agentValue
+ * the client-supplied agent capability, without the leading
+ * "agent="
+ * @return this builder
+ */
+ Builder setAgent(@Nullable String agentValue) {
+ agent = agentValue;
+ return this;
+ }
+
+ /**
+ * Records an application-specific option supplied in a server-option
+ * line, for later retrieval with
+ * {@link FetchV2Request#getServerOptions}.
+ *
+ * @param value
+ * the client-supplied server-option capability, without
+ * leading "server-option=".
+ * @return this builder
+ */
+ Builder addServerOption(@NonNull String value) {
+ serverOptions.add(value);
+ return this;
+ }
+
/**
* @return Initialized fetch request
*/
FetchV2Request build() {
- return new FetchV2Request(peerHas, wantedRefs, wantsIds,
+ return new FetchV2Request(peerHas, wantedRefs, wantIds,
clientShallowCommits, deepenSince, deepenNotRefs,
- depth, filterBlobLimit, doneReceived, options);
+ depth, filterBlobLimit, doneReceived, clientCapabilities,
+ agent, Collections.unmodifiableList(serverOptions));
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java
new file mode 100644
index 0000000000..39e87b246c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An interface providing FTP operations over a {@link RemoteSession}. All
+ * operations are supposed to throw {@link FtpException} for remote file system
+ * errors and other IOExceptions on connection errors.
+ *
+ * @since 5.2
+ */
+public interface FtpChannel {
+
+ /**
+ * An {@link Exception} for reporting SFTP errors.
+ */
+ static class FtpException extends IOException {
+
+ private static final long serialVersionUID = 7176525179280330876L;
+
+ public static final int OK = 0;
+
+ public static final int EOF = 1;
+
+ public static final int NO_SUCH_FILE = 2;
+
+ public static final int NO_PERMISSION = 3;
+
+ public static final int UNSPECIFIED_FAILURE = 4;
+
+ public static final int PROTOCOL_ERROR = 5;
+
+ public static final int UNSUPPORTED = 8;
+
+ private final int status;
+
+ public FtpException(String message, int status) {
+ super(message);
+ this.status = status;
+ }
+
+ public FtpException(String message, int status, Throwable cause) {
+ super(message, cause);
+ this.status = status;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+ }
+
+ /**
+ * Connects the {@link FtpChannel} to the remote end.
+ *
+ * @param timeout
+ * for establishing the FTP connection
+ * @param unit
+ * of the {@code timeout}
+ * @throws IOException
+ */
+ void connect(int timeout, TimeUnit unit) throws IOException;
+
+ /**
+ * Disconnects and {@link FtpChannel}.
+ */
+ void disconnect();
+
+ /**
+ * @return whether the {@link FtpChannel} is connected
+ */
+ boolean isConnected();
+
+ /**
+ * Changes the current remote directory.
+ *
+ * @param path
+ * target directory
+ * @throws IOException
+ * if the operation could not be performed remotely
+ */
+ void cd(String path) throws IOException;
+
+ /**
+ * @return the current remote directory path
+ * @throws IOException
+ */
+ String pwd() throws IOException;
+
+ /**
+ * Simplified remote directory entry.
+ */
+ interface DirEntry {
+ String getFilename();
+
+ long getModifiedTime();
+
+ boolean isDirectory();
+ }
+
+ /**
+ * Lists contents of a remote directory
+ *
+ * @param path
+ * of the directory to list
+ * @return the directory entries
+ * @throws IOException
+ */
+ Collection<DirEntry> ls(String path) throws IOException;
+
+ /**
+ * Deletes a directory on the remote file system. The directory must be
+ * empty.
+ *
+ * @param path
+ * to delete
+ * @throws IOException
+ */
+ void rmdir(String path) throws IOException;
+
+ /**
+ * Creates a directory on the remote file system.
+ *
+ * @param path
+ * to create
+ * @throws IOException
+ */
+ void mkdir(String path) throws IOException;
+
+ /**
+ * Obtain an {@link InputStream} to read the contents of a remote file.
+ *
+ * @param path
+ * of the file to read
+ *
+ * @return the stream to read from
+ * @throws IOException
+ */
+ InputStream get(String path) throws IOException;
+
+ /**
+ * Obtain an {@link OutputStream} to write to a remote file. If the file
+ * exists already, it will be overwritten.
+ *
+ * @param path
+ * of the file to read
+ *
+ * @return the stream to read from
+ * @throws IOException
+ */
+ OutputStream put(String path) throws IOException;
+
+ /**
+ * Deletes a file on the remote file system.
+ *
+ * @param path
+ * to delete
+ * @throws IOException
+ * if the file does not exist or could otherwise not be deleted
+ */
+ void rm(String path) throws IOException;
+
+ /**
+ * Deletes a file on the remote file system. If the file does not exist, no
+ * exception is thrown.
+ *
+ * @param path
+ * to delete
+ * @throws IOException
+ * if the file exist but could not be deleted
+ */
+ default void delete(String path) throws IOException {
+ try {
+ rm(path);
+ } catch (FileNotFoundException e) {
+ // Ignore; it's OK if the file doesn't exist
+ } catch (FtpException f) {
+ if (f.getStatus() == FtpException.NO_SUCH_FILE) {
+ return;
+ }
+ throw f;
+ }
+ }
+
+ /**
+ * Renames a file on the remote file system. If {@code to} exists, it is
+ * replaced by {@code from}. (POSIX rename() semantics)
+ *
+ * @param from
+ * original name of the file
+ * @param to
+ * new name of the file
+ * @throws IOException
+ * @see <a href=
+ * "http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html">stdio.h:
+ * rename()</a>
+ */
+ void rename(String from, String to) throws IOException;
+
+}
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 760ac6c1d7..1561c93b95 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -245,6 +245,20 @@ public final class GitProtocolConstants {
public static final String CAPABILITY_REF_IN_WANT = "ref-in-want"; //$NON-NLS-1$
/**
+ * The server supports arbitrary options
+ *
+ * @since 5.2
+ */
+ public static final String CAPABILITY_SERVER_OPTION = "server-option"; //$NON-NLS-1$
+
+ /**
+ * Option for passing application-specific options to the server.
+ *
+ * @since 5.2
+ */
+ public static final String OPTION_SERVER_OPTION = "server-option"; //$NON-NLS-1$
+
+ /**
* The server supports listing refs using protocol v2.
*
* @since 5.0
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
index 732be63dc1..f05e0b8c7d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
@@ -46,6 +46,7 @@ package org.eclipse.jgit.transport;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
+import java.io.UncheckedIOException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText;
@@ -103,10 +104,13 @@ class InternalPushConnection<C> extends BasePackPushConnection {
// Ignored. Client cannot use this repository.
} catch (ServiceNotAuthorizedException e) {
// Ignored. Client cannot use this repository.
- } catch (IOException err) {
- // Client side of the pipes should report the problem.
- } catch (RuntimeException err) {
- // Clients side will notice we went away, and report.
+ } catch (IOException e) {
+ // Since the InternalPushConnection
+ // is used in tests, we want to avoid hiding exceptions
+ // because they can point to programming errors on the server
+ // side. By rethrowing, the default handler will dump it
+ // to stderr.
+ throw new UncheckedIOException(e);
} finally {
try {
out_r.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
index 4e712a5567..0bdd6ba812 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
@@ -52,7 +52,6 @@ package org.eclipse.jgit.transport;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
-import static org.eclipse.jgit.transport.OpenSshConfig.SSH_PORT;
import java.io.File;
import java.io.FileInputStream;
@@ -275,10 +274,11 @@ public abstract class JschConfigSessionFactory extends SshSessionFactory {
}
private static String hostName(Session s) {
- if (s.getPort() == SSH_PORT) {
+ if (s.getPort() == SshConstants.SSH_DEFAULT_PORT) {
return s.getHost();
}
- return String.format("[%s]:%d", s.getHost(), s.getPort()); //$NON-NLS-1$
+ return String.format("[%s]:%d", s.getHost(), //$NON-NLS-1$
+ Integer.valueOf(s.getPort()));
}
private void copyConfigValueToSession(Session session, Config cfg,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
index e3ef832343..843b90c951 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
@@ -52,6 +52,11 @@ import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText;
@@ -59,8 +64,10 @@ import org.eclipse.jgit.util.io.IsolatedOutputStream;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SftpException;
/**
* Run remote commands using Jsch.
@@ -109,12 +116,24 @@ public class JschSession implements RemoteSession {
* @return a channel suitable for Sftp operations.
* @throws com.jcraft.jsch.JSchException
* on problems getting the channel.
+ * @deprecated since 5.2; use {@link #getFtpChannel()} instead
*/
+ @Deprecated
public Channel getSftpChannel() throws JSchException {
return sock.openChannel("sftp"); //$NON-NLS-1$
}
/**
+ * {@inheritDoc}
+ *
+ * @since 5.2
+ */
+ @Override
+ public FtpChannel getFtpChannel() {
+ return new JschFtpChannel();
+ }
+
+ /**
* Implementation of Process for running a single command using Jsch.
* <p>
* Uses the Jsch session to do actual command execution and manage the
@@ -233,4 +252,154 @@ public class JschSession implements RemoteSession {
return exitValue();
}
}
+
+ private class JschFtpChannel implements FtpChannel {
+
+ private ChannelSftp ftp;
+
+ @Override
+ public void connect(int timeout, TimeUnit unit) throws IOException {
+ try {
+ ftp = (ChannelSftp) sock.openChannel("sftp"); //$NON-NLS-1$
+ ftp.connect((int) unit.toMillis(timeout));
+ } catch (JSchException e) {
+ ftp = null;
+ throw new IOException(e.getLocalizedMessage(), e);
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ ftp.disconnect();
+ ftp = null;
+ }
+
+ private <T> T map(Callable<T> op) throws IOException {
+ try {
+ return op.call();
+ } catch (Exception e) {
+ if (e instanceof SftpException) {
+ throw new FtpChannel.FtpException(e.getLocalizedMessage(),
+ ((SftpException) e).id, e);
+ }
+ throw new IOException(e.getLocalizedMessage(), e);
+ }
+ }
+
+ @Override
+ public boolean isConnected() {
+ return ftp != null && sock.isConnected();
+ }
+
+ @Override
+ public void cd(String path) throws IOException {
+ map(() -> {
+ ftp.cd(path);
+ return null;
+ });
+ }
+
+ @Override
+ public String pwd() throws IOException {
+ return map(() -> ftp.pwd());
+ }
+
+ @Override
+ public Collection<DirEntry> ls(String path) throws IOException {
+ return map(() -> {
+ List<DirEntry> result = new ArrayList<>();
+ for (Object e : ftp.ls(path)) {
+ ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) e;
+ result.add(new DirEntry() {
+
+ @Override
+ public String getFilename() {
+ return entry.getFilename();
+ }
+
+ @Override
+ public long getModifiedTime() {
+ return entry.getAttrs().getMTime();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return entry.getAttrs().isDir();
+ }
+ });
+ }
+ return result;
+ });
+ }
+
+ @Override
+ public void rmdir(String path) throws IOException {
+ map(() -> {
+ ftp.rm(path);
+ return null;
+ });
+ }
+
+ @Override
+ public void mkdir(String path) throws IOException {
+ map(() -> {
+ ftp.mkdir(path);
+ return null;
+ });
+ }
+
+ @Override
+ public InputStream get(String path) throws IOException {
+ return map(() -> ftp.get(path));
+ }
+
+ @Override
+ public OutputStream put(String path) throws IOException {
+ return map(() -> ftp.put(path));
+ }
+
+ @Override
+ public void rm(String path) throws IOException {
+ map(() -> {
+ ftp.rm(path);
+ return null;
+ });
+ }
+
+ @Override
+ public void rename(String from, String to) throws IOException {
+ map(() -> {
+ // Plain FTP rename will fail if "to" exists. Jsch knows about
+ // the FTP extension "posix-rename@openssh.com", which will
+ // remove "to" first if it exists.
+ if (hasPosixRename()) {
+ ftp.rename(from, to);
+ } else if (!to.equals(from)) {
+ // Try to remove "to" first. With git, we typically get this
+ // when a lock file is moved over the file locked. Note that
+ // the check for to being equal to from may still fail in
+ // the general case, but for use with JGit's TransportSftp
+ // it should be good enough.
+ delete(to);
+ ftp.rename(from, to);
+ }
+ return null;
+ });
+ }
+
+ /**
+ * Determine whether the server has the posix-rename extension.
+ *
+ * @return {@code true} if it is supported, {@code false} otherwise
+ * @see <a href=
+ * "https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD">OpenSSH
+ * deviations and extensions to the published SSH protocol</a>
+ * @see <a href=
+ * "http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html">stdio.h:
+ * rename()</a>
+ */
+ private boolean hasPosixRename() {
+ return "1".equals(ftp.getExtension("posix-rename@openssh.com")); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
index 3aff584a00..add373147c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
@@ -42,9 +42,15 @@
*/
package org.eclipse.jgit.transport;
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
/**
* ls-refs protocol v2 request.
*
@@ -60,11 +66,20 @@ public final class LsRefsV2Request {
private final boolean peel;
+ @Nullable
+ private final String agent;
+
+ @NonNull
+ private final List<String> serverOptions;
+
private LsRefsV2Request(List<String> refPrefixes, boolean symrefs,
- boolean peel) {
+ boolean peel, @Nullable String agent,
+ @NonNull List<String> serverOptions) {
this.refPrefixes = refPrefixes;
this.symrefs = symrefs;
this.peel = peel;
+ this.agent = agent;
+ this.serverOptions = requireNonNull(serverOptions);
}
/** @return ref prefixes that the client requested. */
@@ -82,6 +97,34 @@ public final class LsRefsV2Request {
return peel;
}
+ /**
+ * @return agent as reported by the client
+ *
+ * @since 5.2
+ */
+ @Nullable
+ public String getAgent() {
+ return agent;
+ }
+
+ /**
+ * Get application-specific options provided by the client using
+ * --server-option.
+ * <p>
+ * It returns just the content, without the "server-option=" prefix. E.g. a
+ * request with server-option=A and server-option=B lines returns the list
+ * [A, B].
+ *
+ * @return application-specific options from the client as an unmodifiable
+ * list
+ *
+ * @since 5.2
+ */
+ @NonNull
+ public List<String> getServerOptions() {
+ return serverOptions;
+ }
+
/** @return A builder of {@link LsRefsV2Request}. */
public static Builder builder() {
return new Builder();
@@ -95,6 +138,10 @@ public final class LsRefsV2Request {
private boolean peel;
+ private final List<String> serverOptions = new ArrayList<>();
+
+ private String agent;
+
private Builder() {
}
@@ -125,10 +172,43 @@ public final class LsRefsV2Request {
return this;
}
+ /**
+ * Records an application-specific option supplied in a server-option
+ * line, for later retrieval with
+ * {@link LsRefsV2Request#getServerOptions}.
+ *
+ * @param value
+ * the client-supplied server-option capability, without
+ * leading "server-option=".
+ * @return this builder
+ * @since 5.2
+ */
+ public Builder addServerOption(@NonNull String value) {
+ serverOptions.add(value);
+ return this;
+ }
+
+ /**
+ * Value of an agent line received after the command and before the
+ * arguments. E.g. "agent=a.b.c/1.0" should set "a.b.c/1.0".
+ *
+ * @param value
+ * the client-supplied agent capability, without leading
+ * "agent="
+ * @return this builder
+ *
+ * @since 5.2
+ */
+ public Builder setAgent(@Nullable String value) {
+ agent = value;
+ return this;
+ }
+
/** @return LsRefsV2Request */
public LsRefsV2Request build() {
return new LsRefsV2Request(
- Collections.unmodifiableList(refPrefixes), symrefs, peel);
+ Collections.unmodifiableList(refPrefixes), symrefs, peel,
+ agent, Collections.unmodifiableList(serverOptions));
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
index 4f1eba66d3..7dd019ba27 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
@@ -42,10 +42,13 @@
package org.eclipse.jgit.transport;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileReader;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.time.Instant;
import java.util.Collection;
import java.util.HashMap;
@@ -214,7 +217,8 @@ public class NetRC {
this.hosts.clear();
this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc);
- try (BufferedReader r = new BufferedReader(new FileReader(netrc))) {
+ try (BufferedReader r = new BufferedReader(
+ new InputStreamReader(new FileInputStream(netrc), UTF_8))) {
String line = null;
NetRCEntry entry = new NetRCEntry();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
index 51fe9070c0..fc22034340 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
@@ -66,7 +66,7 @@ public interface NonceGenerator {
* @return The nonce to be signed by the pusher
* @throws java.lang.IllegalStateException
*/
- public String createNonce(Repository db, long timestamp)
+ String createNonce(Repository db, long timestamp)
throws IllegalStateException;
/**
@@ -91,6 +91,6 @@ public interface NonceGenerator {
* @return a NonceStatus indicating the trustworthiness of the received
* nonce.
*/
- public NonceStatus verify(String received, String sent,
+ NonceStatus verify(String received, String sent,
Repository db, boolean allowSlop, int slop);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
index 4dd5df9cd6..32e1dff234 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2017, Google Inc.
+ * Copyright (C) 2008, 2018, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -43,30 +43,16 @@
package org.eclipse.jgit.transport;
-import java.io.BufferedReader;
+import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive;
+
import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.Set;
+import java.util.TreeMap;
-import org.eclipse.jgit.errors.InvalidPatternException;
-import org.eclipse.jgit.fnmatch.FileNameMatcher;
-import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.HostEntry;
import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.StringUtils;
-import org.eclipse.jgit.util.SystemReader;
import com.jcraft.jsch.ConfigRepository;
@@ -84,8 +70,7 @@ import com.jcraft.jsch.ConfigRepository;
* <li>JSch's OpenSSHConfig doesn't monitor for config file changes.
* </ul>
* <p>
- * Therefore implement our own parser to read an OpenSSH configuration file. It
- * makes the critical options available to
+ * This parser makes the critical options available to
* {@link org.eclipse.jgit.transport.SshSessionFactory} via
* {@link org.eclipse.jgit.transport.OpenSshConfig.Host} objects returned by
* {@link #lookup(String)}, and implements a fully conforming
@@ -93,49 +78,11 @@ import com.jcraft.jsch.ConfigRepository;
* {@link com.jcraft.jsch.ConfigRepository.Config}s via
* {@link #getConfig(String)}.
* </p>
- * <p>
- * Limitations compared to the full OpenSSH 7.5 parser:
- * </p>
- * <ul>
- * <li>This parser does not handle Match or Include keywords.
- * <li>This parser does not do host name canonicalization (Jsch ignores it
- * anyway).
- * </ul>
- * <p>
- * Note that OpenSSH's readconf.c is a validating parser; Jsch's
- * ConfigRepository OTOH treats all option values as plain strings, so any
- * validation must happen in Jsch outside of the parser. Thus this parser does
- * not validate option values, except for a few options when constructing a
- * {@link org.eclipse.jgit.transport.OpenSshConfig.Host} object.
- * </p>
- * <p>
- * This config does %-substitutions for the following tokens:
- * </p>
- * <ul>
- * <li>%% - single %
- * <li>%C - short-hand for %l%h%p%r. See %p and %r below; the replacement may be
- * done partially only and may leave %p or %r or both unreplaced.
- * <li>%d - home directory path
- * <li>%h - remote host name
- * <li>%L - local host name without domain
- * <li>%l - FQDN of the local host
- * <li>%n - host name as specified in {@link #lookup(String)}
- * <li>%p - port number; replaced only if set in the config
- * <li>%r - remote user name; replaced only if set in the config
- * <li>%u - local user name
- * </ul>
- * <p>
- * If the config doesn't set the port or the remote user name, %p and %r remain
- * un-substituted. It's the caller's responsibility to replace them with values
- * obtained from the connection URI. %i is not handled; Java has no concept of a
- * "user ID".
- * </p>
+ *
+ * @see OpenSshConfigFile
*/
public class OpenSshConfig implements ConfigRepository {
- /** IANA assigned port number for SSH. */
- static final int SSH_PORT = 22;
-
/**
* Obtain the user's configuration data.
* <p>
@@ -154,43 +101,17 @@ public class OpenSshConfig implements ConfigRepository {
if (home == null)
home = new File(".").getAbsoluteFile(); //$NON-NLS-1$
- final File config = new File(new File(home, ".ssh"), Constants.CONFIG); //$NON-NLS-1$
- final OpenSshConfig osc = new OpenSshConfig(home, config);
- osc.refresh();
- return osc;
+ final File config = new File(new File(home, SshConstants.SSH_DIR),
+ SshConstants.CONFIG);
+ return new OpenSshConfig(home, config);
}
- /** The user's home directory, as key files may be relative to here. */
- private final File home;
-
- /** The .ssh/config file we read and monitor for updates. */
- private final File configFile;
-
- /** Modification time of {@link #configFile} when it was last loaded. */
- private Instant lastModified;
-
- /**
- * Encapsulates entries read out of the configuration file, and
- * {@link Host}s created from that.
- */
- private static class State {
- Map<String, HostEntry> entries = new LinkedHashMap<>();
- Map<String, Host> hosts = new HashMap<>();
-
- @Override
- @SuppressWarnings("nls")
- public String toString() {
- return "State [entries=" + entries + ", hosts=" + hosts + "]";
- }
- }
-
- /** State read from the config file, plus {@link Host}s created from it. */
- private State state;
+ /** The base file. */
+ private OpenSshConfigFile configFile;
OpenSshConfig(File h, File cfg) {
- home = h;
- configFile = cfg;
- state = new State();
+ configFile = new OpenSshConfigFile(h, cfg,
+ SshSessionFactory.getLocalUserName());
}
/**
@@ -203,603 +124,8 @@ public class OpenSshConfig implements ConfigRepository {
* @return r configuration for the requested name. Never null.
*/
public Host lookup(String hostName) {
- final State cache = refresh();
- Host h = cache.hosts.get(hostName);
- if (h != null) {
- return h;
- }
- HostEntry fullConfig = new HostEntry();
- // Initialize with default entries at the top of the file, before the
- // first Host block.
- fullConfig.merge(cache.entries.get(HostEntry.DEFAULT_NAME));
- for (Map.Entry<String, HostEntry> e : cache.entries.entrySet()) {
- String key = e.getKey();
- if (isHostMatch(key, hostName)) {
- fullConfig.merge(e.getValue());
- }
- }
- fullConfig.substitute(hostName, home);
- h = new Host(fullConfig, hostName, home);
- cache.hosts.put(hostName, h);
- return h;
- }
-
- private synchronized State refresh() {
- final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
- if (!mtime.equals(lastModified)) {
- State newState = new State();
- try (FileInputStream in = new FileInputStream(configFile)) {
- newState.entries = parse(in);
- } catch (IOException none) {
- // Ignore -- we'll set and return an empty state
- }
- lastModified = mtime;
- state = newState;
- }
- return state;
- }
-
- private Map<String, HostEntry> parse(InputStream in)
- throws IOException {
- final Map<String, HostEntry> m = new LinkedHashMap<>();
- final BufferedReader br = new BufferedReader(new InputStreamReader(in));
- final List<HostEntry> current = new ArrayList<>(4);
- String line;
-
- // The man page doesn't say so, but the OpenSSH parser (readconf.c)
- // starts out in active mode and thus always applies any lines that
- // occur before the first host block. We gather those options in a
- // HostEntry for DEFAULT_NAME.
- HostEntry defaults = new HostEntry();
- current.add(defaults);
- m.put(HostEntry.DEFAULT_NAME, defaults);
-
- while ((line = br.readLine()) != null) {
- line = line.trim();
- if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
- continue;
- }
- String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
- // Although the ssh-config man page doesn't say so, the OpenSSH
- // parser does allow quoted keywords.
- String keyword = dequote(parts[0].trim());
- // man 5 ssh-config says lines had the format "keyword arguments",
- // with no indication that arguments were optional. However, let's
- // not crap out on missing arguments. See bug 444319.
- String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$
-
- if (StringUtils.equalsIgnoreCase("Host", keyword)) { //$NON-NLS-1$
- current.clear();
- for (String name : HostEntry.parseList(argValue)) {
- if (name == null || name.isEmpty()) {
- // null should not occur, but better be safe than sorry.
- continue;
- }
- HostEntry c = m.get(name);
- if (c == null) {
- c = new HostEntry();
- m.put(name, c);
- }
- current.add(c);
- }
- continue;
- }
-
- if (current.isEmpty()) {
- // We received an option outside of a Host block. We
- // don't know who this should match against, so skip.
- continue;
- }
-
- if (HostEntry.isListKey(keyword)) {
- List<String> args = HostEntry.parseList(argValue);
- for (HostEntry entry : current) {
- entry.setValue(keyword, args);
- }
- } else if (!argValue.isEmpty()) {
- argValue = dequote(argValue);
- for (HostEntry entry : current) {
- entry.setValue(keyword, argValue);
- }
- }
- }
-
- return m;
- }
-
- private static boolean isHostMatch(final String pattern,
- final String name) {
- if (pattern.startsWith("!")) { //$NON-NLS-1$
- return !patternMatchesHost(pattern.substring(1), name);
- } else {
- return patternMatchesHost(pattern, name);
- }
- }
-
- private static boolean patternMatchesHost(final String pattern,
- final String name) {
- if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
- final FileNameMatcher fn;
- try {
- fn = new FileNameMatcher(pattern, null);
- } catch (InvalidPatternException e) {
- return false;
- }
- fn.append(name);
- return fn.isMatch();
- } else {
- // Not a pattern but a full host name
- return pattern.equals(name);
- }
- }
-
- private static String dequote(String value) {
- if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$
- && value.length() > 1)
- return value.substring(1, value.length() - 1);
- return value;
- }
-
- private static String nows(String value) {
- final StringBuilder b = new StringBuilder();
- for (int i = 0; i < value.length(); i++) {
- if (!Character.isSpaceChar(value.charAt(i)))
- b.append(value.charAt(i));
- }
- return b.toString();
- }
-
- private static Boolean yesno(String value) {
- if (StringUtils.equalsIgnoreCase("yes", value)) //$NON-NLS-1$
- return Boolean.TRUE;
- return Boolean.FALSE;
- }
-
- private static File toFile(String path, File home) {
- if (path.startsWith("~/")) { //$NON-NLS-1$
- return new File(home, path.substring(2));
- }
- File ret = new File(path);
- if (ret.isAbsolute()) {
- return ret;
- }
- return new File(home, path);
- }
-
- private static int positive(String value) {
- if (value != null) {
- try {
- return Integer.parseUnsignedInt(value);
- } catch (NumberFormatException e) {
- // Ignore
- }
- }
- return -1;
- }
-
- static String userName() {
- return AccessController.doPrivileged(new PrivilegedAction<String>() {
- @Override
- public String run() {
- return SystemReader.getInstance()
- .getProperty(Constants.OS_USER_NAME_KEY);
- }
- });
- }
-
- private static class HostEntry implements ConfigRepository.Config {
-
- /**
- * "Host name" of the HostEntry for the default options before the first
- * host block in a config file.
- */
- public static final String DEFAULT_NAME = ""; //$NON-NLS-1$
-
- // See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys
- // to ssh-config keys.
- private static final Map<String, String> KEY_MAP = new HashMap<>();
-
- static {
- KEY_MAP.put("kex", "KexAlgorithms"); //$NON-NLS-1$//$NON-NLS-2$
- KEY_MAP.put("server_host_key", "HostKeyAlgorithms"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("cipher.c2s", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("cipher.s2c", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("mac.c2s", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("mac.s2c", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("compression.s2c", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("compression.c2s", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$
- KEY_MAP.put("MaxAuthTries", "NumberOfPasswordPrompts"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- /**
- * Keys that can be specified multiple times, building up a list. (I.e.,
- * those are the keys that do not follow the general rule of "first
- * occurrence wins".)
- */
- private static final Set<String> MULTI_KEYS = new HashSet<>();
-
- static {
- MULTI_KEYS.add("CERTIFICATEFILE"); //$NON-NLS-1$
- MULTI_KEYS.add("IDENTITYFILE"); //$NON-NLS-1$
- MULTI_KEYS.add("LOCALFORWARD"); //$NON-NLS-1$
- MULTI_KEYS.add("REMOTEFORWARD"); //$NON-NLS-1$
- MULTI_KEYS.add("SENDENV"); //$NON-NLS-1$
- }
-
- /**
- * Keys that take a whitespace-separated list of elements as argument.
- * Because the dequote-handling is different, we must handle those in
- * the parser. There are a few other keys that take comma-separated
- * lists as arguments, but for the parser those are single arguments
- * that must be quoted if they contain whitespace, and taking them apart
- * is the responsibility of the user of those keys.
- */
- private static final Set<String> LIST_KEYS = new HashSet<>();
-
- static {
- LIST_KEYS.add("CANONICALDOMAINS"); //$NON-NLS-1$
- LIST_KEYS.add("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$
- LIST_KEYS.add("SENDENV"); //$NON-NLS-1$
- LIST_KEYS.add("USERKNOWNHOSTSFILE"); //$NON-NLS-1$
- }
-
- private Map<String, String> options;
-
- private Map<String, List<String>> multiOptions;
-
- private Map<String, List<String>> listOptions;
-
- @Override
- public String getHostname() {
- return getValue("HOSTNAME"); //$NON-NLS-1$
- }
-
- @Override
- public String getUser() {
- return getValue("USER"); //$NON-NLS-1$
- }
-
- @Override
- public int getPort() {
- return positive(getValue("PORT")); //$NON-NLS-1$
- }
-
- private static String mapKey(String key) {
- String k = KEY_MAP.get(key);
- if (k == null) {
- k = key;
- }
- return k.toUpperCase(Locale.ROOT);
- }
-
- private String findValue(String key) {
- String k = mapKey(key);
- String result = options != null ? options.get(k) : null;
- if (result == null) {
- // Also check the list and multi options. Modern OpenSSH treats
- // UserKnownHostsFile and GlobalKnownHostsFile as list-valued,
- // and so does this parser. Jsch 0.1.54 in general doesn't know
- // about list-valued options (it _does_ know multi-valued
- // options, though), and will ask for a single value for such
- // options.
- //
- // Let's be lenient and return at least the first value from
- // a list-valued or multi-valued key for which Jsch asks for a
- // single value.
- List<String> values = listOptions != null ? listOptions.get(k)
- : null;
- if (values == null) {
- values = multiOptions != null ? multiOptions.get(k) : null;
- }
- if (values != null && !values.isEmpty()) {
- result = values.get(0);
- }
- }
- return result;
- }
-
- @Override
- public String getValue(String key) {
- // See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue() for this
- // special case.
- if (key.equals("compression.s2c") //$NON-NLS-1$
- || key.equals("compression.c2s")) { //$NON-NLS-1$
- String foo = findValue(key);
- if (foo == null || foo.equals("no")) { //$NON-NLS-1$
- return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$
- }
- return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$
- }
- return findValue(key);
- }
-
- @Override
- public String[] getValues(String key) {
- String k = mapKey(key);
- List<String> values = listOptions != null ? listOptions.get(k)
- : null;
- if (values == null) {
- values = multiOptions != null ? multiOptions.get(k) : null;
- }
- if (values == null || values.isEmpty()) {
- return new String[0];
- }
- return values.toArray(new String[0]);
- }
-
- public void setValue(String key, String value) {
- String k = key.toUpperCase(Locale.ROOT);
- if (MULTI_KEYS.contains(k)) {
- if (multiOptions == null) {
- multiOptions = new HashMap<>();
- }
- List<String> values = multiOptions.get(k);
- if (values == null) {
- values = new ArrayList<>(4);
- multiOptions.put(k, values);
- }
- values.add(value);
- } else {
- if (options == null) {
- options = new HashMap<>();
- }
- if (!options.containsKey(k)) {
- options.put(k, value);
- }
- }
- }
-
- public void setValue(String key, List<String> values) {
- if (values.isEmpty()) {
- // Can occur only on a missing argument: ignore.
- return;
- }
- String k = key.toUpperCase(Locale.ROOT);
- // Check multi-valued keys first; because of the replacement
- // strategy, they must take precedence over list-valued keys
- // which always follow the "first occurrence wins" strategy.
- //
- // Note that SendEnv is a multi-valued list-valued key. (It's
- // rather immaterial for JGit, though.)
- if (MULTI_KEYS.contains(k)) {
- if (multiOptions == null) {
- multiOptions = new HashMap<>(2 * MULTI_KEYS.size());
- }
- List<String> items = multiOptions.get(k);
- if (items == null) {
- items = new ArrayList<>(values);
- multiOptions.put(k, items);
- } else {
- items.addAll(values);
- }
- } else {
- if (listOptions == null) {
- listOptions = new HashMap<>(2 * LIST_KEYS.size());
- }
- if (!listOptions.containsKey(k)) {
- listOptions.put(k, values);
- }
- }
- }
-
- public static boolean isListKey(String key) {
- return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
- }
-
- /**
- * Splits the argument into a list of whitespace-separated elements.
- * Elements containing whitespace must be quoted and will be de-quoted.
- *
- * @param argument
- * argument part of the configuration line as read from the
- * config file
- * @return a {@link List} of elements, possibly empty and possibly
- * containing empty elements
- */
- public static List<String> parseList(String argument) {
- List<String> result = new ArrayList<>(4);
- int start = 0;
- int length = argument.length();
- while (start < length) {
- // Skip whitespace
- if (Character.isSpaceChar(argument.charAt(start))) {
- start++;
- continue;
- }
- if (argument.charAt(start) == '"') {
- int stop = argument.indexOf('"', ++start);
- if (stop < start) {
- // No closing double quote: skip
- break;
- }
- result.add(argument.substring(start, stop));
- start = stop + 1;
- } else {
- int stop = start + 1;
- while (stop < length
- && !Character.isSpaceChar(argument.charAt(stop))) {
- stop++;
- }
- result.add(argument.substring(start, stop));
- start = stop + 1;
- }
- }
- return result;
- }
-
- protected void merge(HostEntry entry) {
- if (entry == null) {
- // Can occur if we could not read the config file
- return;
- }
- if (entry.options != null) {
- if (options == null) {
- options = new HashMap<>();
- }
- for (Map.Entry<String, String> item : entry.options
- .entrySet()) {
- if (!options.containsKey(item.getKey())) {
- options.put(item.getKey(), item.getValue());
- }
- }
- }
- if (entry.listOptions != null) {
- if (listOptions == null) {
- listOptions = new HashMap<>(2 * LIST_KEYS.size());
- }
- for (Map.Entry<String, List<String>> item : entry.listOptions
- .entrySet()) {
- if (!listOptions.containsKey(item.getKey())) {
- listOptions.put(item.getKey(), item.getValue());
- }
- }
-
- }
- if (entry.multiOptions != null) {
- if (multiOptions == null) {
- multiOptions = new HashMap<>(2 * MULTI_KEYS.size());
- }
- for (Map.Entry<String, List<String>> item : entry.multiOptions
- .entrySet()) {
- List<String> values = multiOptions.get(item.getKey());
- if (values == null) {
- values = new ArrayList<>(item.getValue());
- multiOptions.put(item.getKey(), values);
- } else {
- values.addAll(item.getValue());
- }
- }
- }
- }
-
- private class Replacer {
- private final Map<Character, String> replacements = new HashMap<>();
-
- public Replacer(String originalHostName, File home) {
- replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$
- replacements.put(Character.valueOf('d'), home.getPath());
- // Needs special treatment...
- String host = getValue("HOSTNAME"); //$NON-NLS-1$
- replacements.put(Character.valueOf('h'), originalHostName);
- if (host != null && host.indexOf('%') >= 0) {
- host = substitute(host, "h"); //$NON-NLS-1$
- options.put("HOSTNAME", host); //$NON-NLS-1$
- }
- if (host != null) {
- replacements.put(Character.valueOf('h'), host);
- }
- String localhost = SystemReader.getInstance().getHostname();
- replacements.put(Character.valueOf('l'), localhost);
- int period = localhost.indexOf('.');
- if (period > 0) {
- localhost = localhost.substring(0, period);
- }
- replacements.put(Character.valueOf('L'), localhost);
- replacements.put(Character.valueOf('n'), originalHostName);
- replacements.put(Character.valueOf('p'), getValue("PORT")); //$NON-NLS-1$
- replacements.put(Character.valueOf('r'), getValue("USER")); //$NON-NLS-1$
- replacements.put(Character.valueOf('u'), userName());
- replacements.put(Character.valueOf('C'),
- substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- public String substitute(String input, String allowed) {
- if (input == null || input.length() <= 1
- || input.indexOf('%') < 0) {
- return input;
- }
- StringBuilder builder = new StringBuilder();
- int start = 0;
- int length = input.length();
- while (start < length) {
- int percent = input.indexOf('%', start);
- if (percent < 0 || percent + 1 >= length) {
- builder.append(input.substring(start));
- break;
- }
- String replacement = null;
- char ch = input.charAt(percent + 1);
- if (ch == '%' || allowed.indexOf(ch) >= 0) {
- replacement = replacements.get(Character.valueOf(ch));
- }
- if (replacement == null) {
- builder.append(input.substring(start, percent + 2));
- } else {
- builder.append(input.substring(start, percent))
- .append(replacement);
- }
- start = percent + 2;
- }
- return builder.toString();
- }
- }
-
- private List<String> substitute(List<String> values, String allowed,
- Replacer r) {
- List<String> result = new ArrayList<>(values.size());
- for (String value : values) {
- result.add(r.substitute(value, allowed));
- }
- return result;
- }
-
- private List<String> replaceTilde(List<String> values, File home) {
- List<String> result = new ArrayList<>(values.size());
- for (String value : values) {
- result.add(toFile(value, home).getPath());
- }
- return result;
- }
-
- protected void substitute(String originalHostName, File home) {
- Replacer r = new Replacer(originalHostName, home);
- if (multiOptions != null) {
- List<String> values = multiOptions.get("IDENTITYFILE"); //$NON-NLS-1$
- if (values != null) {
- values = substitute(values, "dhlru", r); //$NON-NLS-1$
- values = replaceTilde(values, home);
- multiOptions.put("IDENTITYFILE", values); //$NON-NLS-1$
- }
- values = multiOptions.get("CERTIFICATEFILE"); //$NON-NLS-1$
- if (values != null) {
- values = substitute(values, "dhlru", r); //$NON-NLS-1$
- values = replaceTilde(values, home);
- multiOptions.put("CERTIFICATEFILE", values); //$NON-NLS-1$
- }
- }
- if (listOptions != null) {
- List<String> values = listOptions.get("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$
- if (values != null) {
- values = replaceTilde(values, home);
- listOptions.put("GLOBALKNOWNHOSTSFILE", values); //$NON-NLS-1$
- }
- values = listOptions.get("USERKNOWNHOSTSFILE"); //$NON-NLS-1$
- if (values != null) {
- values = replaceTilde(values, home);
- listOptions.put("USERKNOWNHOSTSFILE", values); //$NON-NLS-1$
- }
- }
- if (options != null) {
- // HOSTNAME already done in Replacer constructor
- String value = options.get("IDENTITYAGENT"); //$NON-NLS-1$
- if (value != null) {
- value = r.substitute(value, "dhlru"); //$NON-NLS-1$
- value = toFile(value, home).getPath();
- options.put("IDENTITYAGENT", value); //$NON-NLS-1$
- }
- }
- // Match is not implemented and would need to be done elsewhere
- // anyway. ControlPath, LocalCommand, ProxyCommand, and
- // RemoteCommand are not used by Jsch.
- }
-
- @Override
- @SuppressWarnings("nls")
- public String toString() {
- return "HostEntry [options=" + options + ", multiOptions="
- + multiOptions + ", listOptions=" + listOptions + "]";
- }
+ HostEntry entry = configFile.lookup(hostName, -1, null);
+ return new Host(entry, hostName, configFile.getLocalUserName());
}
/**
@@ -830,8 +156,34 @@ public class OpenSshConfig implements ConfigRepository {
int connectionAttempts;
+ private HostEntry entry;
+
private Config config;
+ // See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys
+ // to ssh-config keys.
+ private static final Map<String, String> KEY_MAP = new TreeMap<>(
+ String.CASE_INSENSITIVE_ORDER);
+
+ static {
+ KEY_MAP.put("kex", SshConstants.KEX_ALGORITHMS); //$NON-NLS-1$
+ KEY_MAP.put("server_host_key", SshConstants.HOST_KEY_ALGORITHMS); //$NON-NLS-1$
+ KEY_MAP.put("cipher.c2s", SshConstants.CIPHERS); //$NON-NLS-1$
+ KEY_MAP.put("cipher.s2c", SshConstants.CIPHERS); //$NON-NLS-1$
+ KEY_MAP.put("mac.c2s", SshConstants.MACS); //$NON-NLS-1$
+ KEY_MAP.put("mac.s2c", SshConstants.MACS); //$NON-NLS-1$
+ KEY_MAP.put("compression.s2c", SshConstants.COMPRESSION); //$NON-NLS-1$
+ KEY_MAP.put("compression.c2s", SshConstants.COMPRESSION); //$NON-NLS-1$
+ KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("MaxAuthTries", //$NON-NLS-1$
+ SshConstants.NUMBER_OF_PASSWORD_PROMPTS);
+ }
+
+ private static String mapKey(String key) {
+ String k = KEY_MAP.get(key);
+ return k != null ? k : key;
+ }
+
/**
* Creates a new uninitialized {@link Host}.
*/
@@ -839,9 +191,9 @@ public class OpenSshConfig implements ConfigRepository {
// For API backwards compatibility with pre-4.9 JGit
}
- Host(Config config, String hostName, File homeDir) {
- this.config = config;
- complete(hostName, homeDir);
+ Host(HostEntry entry, String hostName, String localUserName) {
+ this.entry = entry;
+ complete(hostName, localUserName);
}
/**
@@ -911,42 +263,84 @@ public class OpenSshConfig implements ConfigRepository {
}
- private void complete(String initialHostName, File homeDir) {
+ private void complete(String initialHostName, String localUserName) {
// Try to set values from the options.
- hostName = config.getHostname();
- user = config.getUser();
- port = config.getPort();
+ hostName = entry.getValue(SshConstants.HOST_NAME);
+ user = entry.getValue(SshConstants.USER);
+ port = positive(entry.getValue(SshConstants.PORT));
connectionAttempts = positive(
- config.getValue("ConnectionAttempts")); //$NON-NLS-1$
- strictHostKeyChecking = config.getValue("StrictHostKeyChecking"); //$NON-NLS-1$
- String value = config.getValue("BatchMode"); //$NON-NLS-1$
- if (value != null) {
- batchMode = yesno(value);
- }
- value = config.getValue("PreferredAuthentications"); //$NON-NLS-1$
- if (value != null) {
- preferredAuthentications = nows(value);
- }
+ entry.getValue(SshConstants.CONNECTION_ATTEMPTS));
+ strictHostKeyChecking = entry
+ .getValue(SshConstants.STRICT_HOST_KEY_CHECKING);
+ batchMode = Boolean.valueOf(OpenSshConfigFile
+ .flag(entry.getValue(SshConstants.BATCH_MODE)));
+ preferredAuthentications = entry
+ .getValue(SshConstants.PREFERRED_AUTHENTICATIONS);
// Fill in defaults if still not set
- if (hostName == null) {
+ if (hostName == null || hostName.isEmpty()) {
hostName = initialHostName;
}
- if (user == null) {
- user = OpenSshConfig.userName();
+ if (user == null || user.isEmpty()) {
+ user = localUserName;
}
if (port <= 0) {
- port = OpenSshConfig.SSH_PORT;
+ port = SshConstants.SSH_DEFAULT_PORT;
}
if (connectionAttempts <= 0) {
connectionAttempts = 1;
}
- String[] identityFiles = config.getValues("IdentityFile"); //$NON-NLS-1$
- if (identityFiles != null && identityFiles.length > 0) {
- identityFile = toFile(identityFiles[0], homeDir);
+ List<String> identityFiles = entry
+ .getValues(SshConstants.IDENTITY_FILE);
+ if (identityFiles != null && !identityFiles.isEmpty()) {
+ identityFile = new File(identityFiles.get(0));
}
}
Config getConfig() {
+ if (config == null) {
+ config = new Config() {
+
+ @Override
+ public String getHostname() {
+ return Host.this.getHostName();
+ }
+
+ @Override
+ public String getUser() {
+ return Host.this.getUser();
+ }
+
+ @Override
+ public int getPort() {
+ return Host.this.getPort();
+ }
+
+ @Override
+ public String getValue(String key) {
+ // See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue()
+ // for this special case.
+ if (key.equals("compression.s2c") //$NON-NLS-1$
+ || key.equals("compression.c2s")) { //$NON-NLS-1$
+ if (!OpenSshConfigFile.flag(
+ Host.this.entry.getValue(mapKey(key)))) {
+ return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$
+ }
+ return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$
+ }
+ return Host.this.entry.getValue(mapKey(key));
+ }
+
+ @Override
+ public String[] getValues(String key) {
+ List<String> values = Host.this.entry
+ .getValues(mapKey(key));
+ if (values == null) {
+ return new String[0];
+ }
+ return values.toArray(new String[0]);
+ }
+ };
+ }
return config;
}
@@ -958,7 +352,7 @@ public class OpenSshConfig implements ConfigRepository {
+ ", preferredAuthentications=" + preferredAuthentications
+ ", batchMode=" + batchMode + ", strictHostKeyChecking="
+ strictHostKeyChecking + ", connectionAttempts="
- + connectionAttempts + ", config=" + config + "]";
+ + connectionAttempts + ", entry=" + entry + "]";
}
}
@@ -978,9 +372,7 @@ public class OpenSshConfig implements ConfigRepository {
/** {@inheritDoc} */
@Override
- @SuppressWarnings("nls")
public String toString() {
- return "OpenSshConfig [home=" + home + ", configFile=" + configFile
- + ", lastModified=" + lastModified + ", state=" + state + "]";
+ return "OpenSshConfig [configFile=" + configFile + ']'; //$NON-NLS-1$
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
index 5cbb6f5dfb..ba5d2f3c8f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
@@ -63,7 +63,7 @@ import java.util.Collection;
*/
public interface PostReceiveHook {
/** A simple no-op hook. */
- public static final PostReceiveHook NULL = new PostReceiveHook() {
+ PostReceiveHook NULL = new PostReceiveHook() {
@Override
public void onPostReceive(final ReceivePack rp,
final Collection<ReceiveCommand> commands) {
@@ -81,6 +81,6 @@ public interface PostReceiveHook {
* unmodifiable set of successfully completed commands. May be
* the empty set.
*/
- public void onPostReceive(ReceivePack rp,
+ void onPostReceive(ReceivePack rp,
Collection<ReceiveCommand> commands);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
index 09667eb01a..3aa3b127e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
@@ -57,7 +57,7 @@ import org.eclipse.jgit.storage.pack.PackStatistics;
*/
public interface PostUploadHook {
/** A simple no-op hook. */
- public static final PostUploadHook NULL = new PostUploadHook() {
+ PostUploadHook NULL = new PostUploadHook() {
@Override
public void onPostUpload(PackStatistics stats) {
// Do nothing.
@@ -72,5 +72,5 @@ public interface PostUploadHook {
* {@link org.eclipse.jgit.internal.storage.pack.PackWriter} for
* the uploaded pack
*/
- public void onPostUpload(PackStatistics stats);
+ void onPostUpload(PackStatistics stats);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
index 77c1a8af29..30845d3b68 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
@@ -79,7 +79,7 @@ import java.util.Collection;
*/
public interface PreReceiveHook {
/** A simple no-op hook. */
- public static final PreReceiveHook NULL = new PreReceiveHook() {
+ PreReceiveHook NULL = new PreReceiveHook() {
@Override
public void onPreReceive(final ReceivePack rp,
final Collection<ReceiveCommand> commands) {
@@ -99,5 +99,5 @@ public interface PreReceiveHook {
* unmodifiable set of valid commands still pending execution.
* May be the empty set.
*/
- public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands);
+ void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
index 2e1cd5800a..65dc241584 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHook.java
@@ -59,7 +59,7 @@ import org.eclipse.jgit.lib.ObjectId;
*/
public interface PreUploadHook {
/** A simple no-op hook. */
- public static final PreUploadHook NULL = new PreUploadHook() {
+ PreUploadHook NULL = new PreUploadHook() {
@Override
public void onBeginNegotiateRound(UploadPack up,
Collection<? extends ObjectId> wants, int cntOffered)
@@ -96,7 +96,7 @@ public interface PreUploadHook {
* @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
* abort; the message will be sent to the user.
*/
- public void onBeginNegotiateRound(UploadPack up,
+ void onBeginNegotiateRound(UploadPack up,
Collection<? extends ObjectId> wants, int cntOffered)
throws ServiceMayNotContinueException;
@@ -120,7 +120,7 @@ public interface PreUploadHook {
* @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
* abort; the message will be sent to the user.
*/
- public void onEndNegotiateRound(UploadPack up,
+ void onEndNegotiateRound(UploadPack up,
Collection<? extends ObjectId> wants, int cntCommon,
int cntNotFound, boolean ready)
throws ServiceMayNotContinueException;
@@ -141,7 +141,7 @@ public interface PreUploadHook {
* @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
* abort; the message will be sent to the user.
*/
- public void onSendPack(UploadPack up, Collection<? extends ObjectId> wants,
+ void onSendPack(UploadPack up, Collection<? extends ObjectId> wants,
Collection<? extends ObjectId> haves)
throws ServiceMayNotContinueException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
new file mode 100644
index 0000000000..21498d6f5c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.transport.parser.FirstWant;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Parser for git protocol versions 0 and 1.
+ *
+ * It reads the lines coming through the {@link PacketLineIn} and builds a
+ * {@link FetchV0Request} object.
+ *
+ * It requires a transferConfig object to know if the server supports filters.
+ */
+final class ProtocolV0Parser {
+
+ private final TransferConfig transferConfig;
+
+ ProtocolV0Parser(TransferConfig transferConfig) {
+ this.transferConfig = transferConfig;
+ }
+
+ /**
+ * Parse an incoming protocol v1 upload request arguments from the wire.
+ *
+ * The incoming PacketLineIn is consumed until an END line, but the caller
+ * is responsible for closing it (if needed).
+ *
+ * @param pckIn
+ * incoming lines. This method will read until an END line.
+ * @return a FetchV0Request with the data received in the wire.
+ * @throws PackProtocolException
+ * @throws IOException
+ */
+ FetchV0Request recvWants(PacketLineIn pckIn)
+ throws PackProtocolException, IOException {
+ FetchV0Request.Builder reqBuilder = new FetchV0Request.Builder();
+
+ boolean isFirst = true;
+ boolean filterReceived = false;
+
+ for (;;) {
+ String line;
+ try {
+ line = pckIn.readString();
+ } catch (EOFException eof) {
+ if (isFirst) {
+ break;
+ }
+ throw eof;
+ }
+
+ if (line == PacketLineIn.END) {
+ break;
+ }
+
+ if (line.startsWith("deepen ")) { //$NON-NLS-1$
+ int depth = Integer.parseInt(line.substring(7));
+ if (depth <= 0) {
+ throw new PackProtocolException(
+ MessageFormat.format(JGitText.get().invalidDepth,
+ Integer.valueOf(depth)));
+ }
+ reqBuilder.setDepth(depth);
+ continue;
+ }
+
+ if (line.startsWith("shallow ")) { //$NON-NLS-1$
+ reqBuilder.addClientShallowCommit(
+ ObjectId.fromString(line.substring(8)));
+ continue;
+ }
+
+ if (transferConfig.isAllowFilter()
+ && line.startsWith(OPTION_FILTER + " ")) { //$NON-NLS-1$
+ String arg = line.substring(OPTION_FILTER.length() + 1);
+
+ if (filterReceived) {
+ throw new PackProtocolException(
+ JGitText.get().tooManyFilters);
+ }
+ filterReceived = true;
+
+ reqBuilder.setFilterBlobLimit(ProtocolV2Parser.filterLine(arg));
+ continue;
+ }
+
+ if (!line.startsWith("want ") || line.length() < 45) { //$NON-NLS-1$
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$
+ }
+
+ if (isFirst) {
+ if (line.length() > 45) {
+ FirstWant firstLine = FirstWant.fromLine(line);
+ reqBuilder.addClientCapabilities(firstLine.getCapabilities());
+ reqBuilder.setAgent(firstLine.getAgent());
+ line = firstLine.getLine();
+ }
+ }
+
+ reqBuilder.addWantId(ObjectId.fromString(line.substring(5)));
+ isFirst = false;
+ }
+
+ return reqBuilder.build();
+ }
+
+}
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 2cc50a7f38..8f4b86ee0a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -44,15 +44,20 @@ package org.eclipse.jgit.transport;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SERVER_OPTION;
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_WANT_REF;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
@@ -73,6 +78,35 @@ final class ProtocolV2Parser {
this.transferConfig = transferConfig;
}
+ /*
+ * Read lines until DELIM or END, calling the appropiate consumer.
+ *
+ * Returns the last read line (so caller can check if there is more to read
+ * in the line).
+ */
+ private static String consumeCapabilities(PacketLineIn pckIn,
+ Consumer<String> serverOptionConsumer,
+ Consumer<String> agentConsumer) throws IOException {
+
+ String serverOptionPrefix = OPTION_SERVER_OPTION + '=';
+ String agentPrefix = OPTION_AGENT + '=';
+
+ String line = pckIn.readString();
+ while (line != PacketLineIn.DELIM && line != PacketLineIn.END) {
+ if (line.startsWith(serverOptionPrefix)) {
+ serverOptionConsumer
+ .accept(line.substring(serverOptionPrefix.length()));
+ } else if (line.startsWith(agentPrefix)) {
+ agentConsumer.accept(line.substring(agentPrefix.length()));
+ } else {
+ // Unrecognized capability. Ignore it.
+ }
+ line = pckIn.readString();
+ }
+
+ return line;
+ }
+
/**
* Parse the incoming fetch request arguments from the wire. The caller must
* be sure that what is comings is a fetch request before coming here.
@@ -93,21 +127,26 @@ final class ProtocolV2Parser {
// Packs are always sent multiplexed and using full 64K
// lengths.
- reqBuilder.addOption(OPTION_SIDE_BAND_64K);
+ reqBuilder.addClientCapability(OPTION_SIDE_BAND_64K);
- String line;
+ String line = consumeCapabilities(pckIn,
+ serverOption -> reqBuilder.addServerOption(serverOption),
+ agent -> reqBuilder.setAgent(agent));
- // Currently, we do not support any capabilities, so the next
- // line is DELIM.
- if ((line = pckIn.readString()) != PacketLineIn.DELIM) {
- throw new PackProtocolException(MessageFormat
- .format(JGitText.get().unexpectedPacketLine, line));
+ if (line == PacketLineIn.END) {
+ return reqBuilder.build();
+ }
+
+ if (line != PacketLineIn.DELIM) {
+ throw new PackProtocolException(
+ MessageFormat.format(JGitText.get().unexpectedPacketLine,
+ line));
}
boolean filterReceived = false;
while ((line = pckIn.readString()) != PacketLineIn.END) {
if (line.startsWith("want ")) { //$NON-NLS-1$
- reqBuilder.addWantsId(ObjectId.fromString(line.substring(5)));
+ reqBuilder.addWantId(ObjectId.fromString(line.substring(5)));
} else if (transferConfig.isAllowRefInWant()
&& line.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$
reqBuilder.addWantedRef(line.substring(OPTION_WANT_REF.length() + 1));
@@ -116,13 +155,13 @@ final class ProtocolV2Parser {
} else if (line.equals("done")) { //$NON-NLS-1$
reqBuilder.setDoneReceived();
} else if (line.equals(OPTION_THIN_PACK)) {
- reqBuilder.addOption(OPTION_THIN_PACK);
+ reqBuilder.addClientCapability(OPTION_THIN_PACK);
} else if (line.equals(OPTION_NO_PROGRESS)) {
- reqBuilder.addOption(OPTION_NO_PROGRESS);
+ reqBuilder.addClientCapability(OPTION_NO_PROGRESS);
} else if (line.equals(OPTION_INCLUDE_TAG)) {
- reqBuilder.addOption(OPTION_INCLUDE_TAG);
+ reqBuilder.addClientCapability(OPTION_INCLUDE_TAG);
} else if (line.equals(OPTION_OFS_DELTA)) {
- reqBuilder.addOption(OPTION_OFS_DELTA);
+ reqBuilder.addClientCapability(OPTION_OFS_DELTA);
} else if (line.startsWith("shallow ")) { //$NON-NLS-1$
reqBuilder.addClientShallowCommit(
ObjectId.fromString(line.substring(8)));
@@ -149,7 +188,7 @@ final class ProtocolV2Parser {
JGitText.get().deepenNotWithDeepen);
}
} else if (line.equals(OPTION_DEEPEN_RELATIVE)) {
- reqBuilder.addOption(OPTION_DEEPEN_RELATIVE);
+ reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE);
} else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$
int ts = Integer.parseInt(line.substring(13));
if (ts <= 0) {
@@ -180,6 +219,57 @@ final class ProtocolV2Parser {
}
/**
+ * Parse the incoming ls-refs request arguments from the wire. This is meant
+ * for calling immediately after the caller has consumed a "command=ls-refs"
+ * line indicating the beginning of a ls-refs request.
+ *
+ * The incoming PacketLineIn is consumed until an END line, but the caller
+ * is responsible for closing it (if needed)
+ *
+ * @param pckIn
+ * incoming lines. This method will read until an END line.
+ * @return a LsRefsV2Request object with the data received in the wire.
+ * @throws PackProtocolException
+ * for inconsistencies in the protocol (e.g. unexpected lines)
+ * @throws IOException
+ * reporting problems reading the incoming messages from the
+ * wire
+ */
+ LsRefsV2Request parseLsRefsRequest(PacketLineIn pckIn)
+ throws PackProtocolException, IOException {
+ LsRefsV2Request.Builder builder = LsRefsV2Request.builder();
+ List<String> prefixes = new ArrayList<>();
+
+ String line = consumeCapabilities(pckIn,
+ serverOption -> builder.addServerOption(serverOption),
+ agent -> builder.setAgent(agent));
+
+ if (line == PacketLineIn.END) {
+ return builder.build();
+ }
+
+ if (line != PacketLineIn.DELIM) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().unexpectedPacketLine, line));
+ }
+
+ while ((line = pckIn.readString()) != PacketLineIn.END) {
+ if (line.equals("peel")) { //$NON-NLS-1$
+ builder.setPeel(true);
+ } else if (line.equals("symrefs")) { //$NON-NLS-1$
+ builder.setSymrefs(true);
+ } else if (line.startsWith("ref-prefix ")) { //$NON-NLS-1$
+ prefixes.add(line.substring("ref-prefix ".length())); //$NON-NLS-1$
+ } else {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().unexpectedPacketLine, line));
+ }
+ }
+
+ return builder.setRefPrefixes(prefixes).build();
+ }
+
+ /*
* Process the content of "filter" line from the protocol. It has a shape
* like "blob:none" or "blob:limit=N", with limit a positive number.
*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
index ff2939a3d6..7f98d4dcc9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
@@ -113,7 +113,7 @@ public interface PushConnection extends Connection {
* created. Non-critical errors concerning only isolated refs
* should be placed in refUpdates.
*/
- public void push(final ProgressMonitor monitor,
+ void push(final ProgressMonitor monitor,
final Map<String, RemoteRefUpdate> refUpdates)
throws TransportException;
@@ -163,7 +163,7 @@ public interface PushConnection extends Connection {
* should be placed in refUpdates.
* @since 3.0
*/
- public void push(final ProgressMonitor monitor,
+ void push(final ProgressMonitor monitor,
final Map<String, RemoteRefUpdate> refUpdates, OutputStream out)
throws TransportException;
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 35fb0b17a7..577aaf4e9e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -76,8 +76,6 @@ public class ReceivePack extends BaseReceivePack {
/** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */
private boolean reportStatus;
- private boolean echoCommandFailures;
-
/** Whether the client intends to use push options. */
private boolean usePushOptions;
private List<String> pushOptions;
@@ -191,9 +189,11 @@ public class ReceivePack extends BaseReceivePack {
* messages before sending the command results. This is usually
* not necessary, but may help buggy Git clients that discard the
* errors when all branches fail.
+ * @deprecated no widely used Git versions need this any more
*/
+ @Deprecated
public void setEchoCommandFailures(boolean echo) {
- echoCommandFailures = echo;
+ // No-op.
}
/**
@@ -269,36 +269,28 @@ public class ReceivePack extends BaseReceivePack {
}
}
- if (unpackError == null) {
- boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
- setAtomic(atomic);
+ try {
+ if (unpackError == null) {
+ boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
+ setAtomic(atomic);
- validateCommands();
- if (atomic && anyRejects())
- failPendingCommands();
+ validateCommands();
+ if (atomic && anyRejects()) {
+ failPendingCommands();
+ }
- preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
- if (atomic && anyRejects())
- failPendingCommands();
- executeCommands();
+ preReceive.onPreReceive(
+ this, filterCommands(Result.NOT_ATTEMPTED));
+ if (atomic && anyRejects()) {
+ failPendingCommands();
+ }
+ executeCommands();
+ }
+ } finally {
+ unlockPack();
}
- unlockPack();
if (reportStatus) {
- if (echoCommandFailures && msgOut != null) {
- sendStatusReport(false, unpackError, new Reporter() {
- @Override
- void sendString(String s) throws IOException {
- msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
- }
- });
- msgOut.flush();
- try {
- Thread.sleep(500);
- } catch (InterruptedException wakeUp) {
- // Ignore an early wake up.
- }
- }
sendStatusReport(true, unpackError, new Reporter() {
@Override
void sendString(String s) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 4662435ea7..6595cab71d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -207,7 +207,8 @@ public abstract class RefAdvertiser {
* <p>
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Map)}
+ * <li>{@link #send(Map)}</li>
+ * <li>{@link #send(Collection)}</li>
* </ul>
*
* @param deref
@@ -223,8 +224,9 @@ public abstract class RefAdvertiser {
* <p>
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Map)}
- * <li>{@link #advertiseHave(AnyObjectId)}
+ * <li>{@link #send(Map)}</li>
+ * <li>{@link #send(Collection)}</li>
+ * <li>{@link #advertiseHave(AnyObjectId)}</li>
* </ul>
*
* @param name
@@ -257,8 +259,9 @@ public abstract class RefAdvertiser {
* <p>
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Map)}
- * <li>{@link #advertiseHave(AnyObjectId)}
+ * <li>{@link #send(Map)}</li>
+ * <li>{@link #send(Collection)}</li>
+ * <li>{@link #advertiseHave(AnyObjectId)}</li>
* </ul>
*
* @param from
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
index 992ddc6e53..d6d6198f5b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
@@ -61,7 +61,7 @@ public interface RefFilter {
/**
* The default filter, allows all refs to be shown.
*/
- public static final RefFilter DEFAULT = new RefFilter() {
+ RefFilter DEFAULT = new RefFilter() {
@Override
public Map<String, Ref> filter (Map<String, Ref> refs) {
return refs;
@@ -76,5 +76,5 @@ public interface RefFilter {
* @return
* the filtered map of refs.
*/
- public Map<String, Ref> filter(Map<String, Ref> refs);
+ Map<String, Ref> filter(Map<String, Ref> refs);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 931653fa90..9a67f0f8fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -521,7 +521,7 @@ public class RemoteRefUpdate {
: "(null)") + "..."
+ (newObjectId != null ? newObjectId.name() : "(null)")
+ (fastForward ? ", fastForward" : "")
- + ", srcRef=" + srcRef
+ + ", srcRef=" + srcRef
+ (forceUpdate ? ", forceUpdate" : "") + ", message="
+ (message != null ? "\"" + message + "\"" : "null") + "]";
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
index 525c895f45..e2109c2c5b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
@@ -78,10 +78,21 @@ public interface RemoteSession {
* a TransportException may be thrown (a subclass of
* java.io.IOException).
*/
- public Process exec(String commandName, int timeout) throws IOException;
+ Process exec(String commandName, int timeout) throws IOException;
+
+ /**
+ * Obtain an {@link FtpChannel} for performing FTP operations over this
+ * {@link RemoteSession}. The default implementation returns {@code null}.
+ *
+ * @return the {@link FtpChannel}
+ * @since 5.2
+ */
+ default FtpChannel getFtpChannel() {
+ return null;
+ }
/**
* Disconnect the remote session
*/
- public void disconnect();
+ void disconnect();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
index 90600cbb98..fde4401289 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
@@ -236,7 +236,7 @@ public class SideBandInputStream extends InputStream {
messages.write(msg);
if (out != null)
- out.write(msg.getBytes());
+ out.write(msg.getBytes(UTF_8));
}
private void beginTask(int totalWorkUnits) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
new file mode 100644
index 0000000000..2b79d7105c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2018 Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.Constants;
+
+/**
+ * Constants relating to ssh.
+ *
+ * @since 5.2
+ */
+@SuppressWarnings("nls")
+public final class SshConstants {
+
+ private SshConstants() {
+ // No instances, please.
+ }
+
+ /** IANA assigned port number for ssh. */
+ public static final int SSH_DEFAULT_PORT = 22;
+
+ /** URI scheme for ssh. */
+ public static final String SSH_SCHEME = "ssh";
+
+ /** URI scheme for sftp. */
+ public static final String SFTP_SCHEME = "sftp";
+
+ /** Default name for a ssh directory. */
+ public static final String SSH_DIR = ".ssh";
+
+ /** Name of the ssh config file. */
+ public static final String CONFIG = Constants.CONFIG;
+
+ /** Default name of the user "known hosts" file. */
+ public static final String KNOWN_HOSTS = "known_hosts";
+
+ // Config file keys
+
+ /** Key in an ssh config file. */
+ public static final String BATCH_MODE = "BatchMode";
+
+ /** Key in an ssh config file. */
+ public static final String CANONICAL_DOMAINS = "CanonicalDomains";
+
+ /** Key in an ssh config file. */
+ public static final String CERTIFICATE_FILE = "CertificateFile";
+
+ /** Key in an ssh config file. */
+ public static final String CIPHERS = "Ciphers";
+
+ /** Key in an ssh config file. */
+ public static final String COMPRESSION = "Compression";
+
+ /** Key in an ssh config file. */
+ public static final String CONNECTION_ATTEMPTS = "ConnectionAttempts";
+
+ /** Key in an ssh config file. */
+ public static final String CONTROL_PATH = "ControlPath";
+
+ /** Key in an ssh config file. */
+ public static final String GLOBAL_KNOWN_HOSTS_FILE = "GlobalKnownHostsFile";
+
+ /** Key in an ssh config file. */
+ public static final String HOST = "Host";
+
+ /** Key in an ssh config file. */
+ public static final String HOST_KEY_ALGORITHMS = "HostKeyAlgorithms";
+
+ /** Key in an ssh config file. */
+ public static final String HOST_NAME = "HostName";
+
+ /** Key in an ssh config file. */
+ public static final String IDENTITIES_ONLY = "IdentitiesOnly";
+
+ /** Key in an ssh config file. */
+ public static final String IDENTITY_AGENT = "IdentityAgent";
+
+ /** Key in an ssh config file. */
+ public static final String IDENTITY_FILE = "IdentityFile";
+
+ /** Key in an ssh config file. */
+ public static final String KEX_ALGORITHMS = "KexAlgorithms";
+
+ /** Key in an ssh config file. */
+ public static final String LOCAL_COMMAND = "LocalCommand";
+
+ /** Key in an ssh config file. */
+ public static final String LOCAL_FORWARD = "LocalForward";
+
+ /** Key in an ssh config file. */
+ public static final String MACS = "MACs";
+
+ /** Key in an ssh config file. */
+ public static final String NUMBER_OF_PASSWORD_PROMPTS = "NumberOfPasswordPrompts";
+
+ /** Key in an ssh config file. */
+ public static final String PORT = "Port";
+
+ /** Key in an ssh config file. */
+ public static final String PREFERRED_AUTHENTICATIONS = "PreferredAuthentications";
+
+ /** Key in an ssh config file. */
+ public static final String PROXY_COMMAND = "ProxyCommand";
+
+ /** Key in an ssh config file. */
+ public static final String REMOTE_COMMAND = "RemoteCommand";
+
+ /** Key in an ssh config file. */
+ public static final String REMOTE_FORWARD = "RemoteForward";
+
+ /** Key in an ssh config file. */
+ public static final String SEND_ENV = "SendEnv";
+
+ /** Key in an ssh config file. */
+ public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
+
+ /** Key in an ssh config file. */
+ public static final String USER = "User";
+
+ /** Key in an ssh config file. */
+ public static final String USER_KNOWN_HOSTS_FILE = "UserKnownHostsFile";
+
+ // Values
+
+ /** Flag value. */
+ public static final String YES = "yes";
+
+ /** Flag value. */
+ public static final String ON = "on";
+
+ /** Flag value. */
+ public static final String TRUE = "true";
+
+ /** Flag value. */
+ public static final String NO = "no";
+
+ /** Flag value. */
+ public static final String OFF = "off";
+
+ /** Flag value. */
+ public static final String FALSE = "false";
+
+ // Default identity file names
+
+ /** Name of the default RSA private identity file. */
+ public static final String ID_RSA = "id_rsa";
+
+ /** Name of the default DSA private identity file. */
+ public static final String ID_DSA = "id_dsa";
+
+ /** Name of the default ECDSA private identity file. */
+ public static final String ID_ECDSA = "id_ecdsa";
+
+ /** Name of the default ECDSA private identity file. */
+ public static final String ID_ED25519 = "id_ed25519";
+
+ /** All known default identity file names. */
+ public static final String[] DEFAULT_IDENTITIES = { //
+ ID_RSA, ID_DSA, ID_ECDSA, ID_ED25519
+ };
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
index ae357dfb75..005a0c2d0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -44,8 +44,13 @@
package org.eclipse.jgit.transport;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.SystemReader;
/**
* Creates and destroys SSH connections to a remote system.
@@ -88,21 +93,38 @@ public abstract class SshSessionFactory {
}
/**
+ * Retrieves the local user name as defined by the system property
+ * "user.name".
+ *
+ * @return the user name
+ * @since 5.2
+ */
+ public static String getLocalUserName() {
+ return AccessController.doPrivileged(new PrivilegedAction<String>() {
+ @Override
+ public String run() {
+ return SystemReader.getInstance()
+ .getProperty(Constants.OS_USER_NAME_KEY);
+ }
+ });
+ }
+
+ /**
* Open (or reuse) a session to a host.
* <p>
* A reasonable UserInfo that can interact with the end-user (if necessary)
* is installed on the returned session by this method.
* <p>
- * The caller must connect the session by invoking <code>connect()</code>
- * if it has not already been connected.
+ * The caller must connect the session by invoking <code>connect()</code> if
+ * it has not already been connected.
*
* @param uri
* URI information about the remote host
* @param credentialsProvider
* provider to support authentication, may be null.
* @param fs
- * the file system abstraction which will be necessary to
- * perform certain file system operations.
+ * the file system abstraction which will be necessary to perform
+ * certain file system operations.
* @param tms
* Timeout value, in milliseconds.
* @return a session that can contact the remote host.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index db95396047..a3e655cd92 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -106,7 +106,8 @@ public class TransferConfig {
this.name = name;
}
- static @Nullable ProtocolVersion parse(@Nullable String name) {
+ @Nullable
+ static ProtocolVersion parse(@Nullable String name) {
if (name == null) {
return null;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
index 6a285e59f5..ee851cc620 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java
@@ -58,5 +58,5 @@ public interface TransportBundle extends PackTransport {
/**
* Bundle signature
*/
- public static final String V2_BUNDLE_SIGNATURE = "# v2 git bundle"; //$NON-NLS-1$
+ String V2_BUNDLE_SIGNATURE = "# v2 git bundle"; //$NON-NLS-1$
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
index f129ba34da..5c68308f90 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -53,13 +53,14 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
@@ -73,12 +74,6 @@ import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
-import com.jcraft.jsch.Channel;
-import com.jcraft.jsch.ChannelSftp;
-import com.jcraft.jsch.JSchException;
-import com.jcraft.jsch.SftpATTRS;
-import com.jcraft.jsch.SftpException;
-
/**
* Transport over the non-Git aware SFTP (SSH based FTP) protocol.
* <p>
@@ -158,24 +153,16 @@ public class TransportSftp extends SshTransport implements WalkTransport {
return r;
}
- ChannelSftp newSftp() throws TransportException {
- final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
- try {
- // @TODO: Fix so that this operation is generic and casting to
- // JschSession is no longer necessary.
- final Channel channel = ((JschSession) getSession())
- .getSftpChannel();
- channel.connect(tms);
- return (ChannelSftp) channel;
- } catch (JSchException je) {
- throw new TransportException(uri, je.getMessage(), je);
- }
+ FtpChannel newSftp() throws IOException {
+ FtpChannel channel = getSession().getFtpChannel();
+ channel.connect(getTimeout(), TimeUnit.SECONDS);
+ return channel;
}
class SftpObjectDB extends WalkRemoteObjectDatabase {
private final String objectsPath;
- private ChannelSftp ftp;
+ private FtpChannel ftp;
SftpObjectDB(String path) throws TransportException {
if (path.startsWith("/~")) //$NON-NLS-1$
@@ -187,13 +174,13 @@ public class TransportSftp extends SshTransport implements WalkTransport {
ftp.cd(path);
ftp.cd("objects"); //$NON-NLS-1$
objectsPath = ftp.pwd();
- } catch (TransportException err) {
- close();
- throw err;
- } catch (SftpException je) {
+ } catch (FtpChannel.FtpException f) {
throw new TransportException(MessageFormat.format(
JGitText.get().cannotEnterObjectsPath, path,
- je.getMessage()), je);
+ f.getMessage()), f);
+ } catch (IOException ioe) {
+ close();
+ throw new TransportException(uri, ioe.getMessage(), ioe);
}
}
@@ -204,13 +191,13 @@ public class TransportSftp extends SshTransport implements WalkTransport {
ftp.cd(parent.objectsPath);
ftp.cd(p);
objectsPath = ftp.pwd();
- } catch (TransportException err) {
- close();
- throw err;
- } catch (SftpException je) {
+ } catch (FtpChannel.FtpException f) {
throw new TransportException(MessageFormat.format(
JGitText.get().cannotEnterPathFromParent, p,
- parent.objectsPath, je.getMessage()), je);
+ parent.objectsPath, f.getMessage()), f);
+ } catch (IOException ioe) {
+ close();
+ throw new TransportException(uri, ioe.getMessage(), ioe);
}
}
@@ -238,41 +225,32 @@ public class TransportSftp extends SshTransport implements WalkTransport {
Collection<String> getPackNames() throws IOException {
final List<String> packs = new ArrayList<>();
try {
- @SuppressWarnings("unchecked")
- final Collection<ChannelSftp.LsEntry> list = ftp.ls("pack"); //$NON-NLS-1$
- final HashMap<String, ChannelSftp.LsEntry> files;
- final HashMap<String, Integer> mtimes;
-
- files = new HashMap<>();
- mtimes = new HashMap<>();
-
- for (ChannelSftp.LsEntry ent : list)
- files.put(ent.getFilename(), ent);
- for (ChannelSftp.LsEntry ent : list) {
- final String n = ent.getFilename();
- if (!n.startsWith("pack-") || !n.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$
+ Collection<FtpChannel.DirEntry> list = ftp.ls("pack"); //$NON-NLS-1$
+ Set<String> files = list.stream()
+ .map(FtpChannel.DirEntry::getFilename)
+ .collect(Collectors.toSet());
+ HashMap<String, Long> mtimes = new HashMap<>();
+
+ for (FtpChannel.DirEntry ent : list) {
+ String n = ent.getFilename();
+ if (!n.startsWith("pack-") || !n.endsWith(".pack")) { //$NON-NLS-1$ //$NON-NLS-2$
continue;
-
- final String in = n.substring(0, n.length() - 5) + ".idx"; //$NON-NLS-1$
- if (!files.containsKey(in))
+ }
+ String in = n.substring(0, n.length() - 5) + ".idx"; //$NON-NLS-1$
+ if (!files.contains(in)) {
continue;
-
- mtimes.put(n, Integer.valueOf(ent.getAttrs().getMTime()));
+ }
+ mtimes.put(n, Long.valueOf(ent.getModifiedTime()));
packs.add(n);
}
- Collections.sort(packs, new Comparator<String>() {
- @Override
- public int compare(String o1, String o2) {
- return mtimes.get(o2).intValue()
- - mtimes.get(o1).intValue();
- }
- });
- } catch (SftpException je) {
+ Collections.sort(packs,
+ (o1, o2) -> mtimes.get(o2).compareTo(mtimes.get(o1)));
+ } catch (FtpChannel.FtpException f) {
throw new TransportException(
MessageFormat.format(JGitText.get().cannotListPackPath,
- objectsPath, je.getMessage()),
- je);
+ objectsPath, f.getMessage()),
+ f);
}
return packs;
}
@@ -280,27 +258,25 @@ public class TransportSftp extends SshTransport implements WalkTransport {
@Override
FileStream open(String path) throws IOException {
try {
- final SftpATTRS a = ftp.lstat(path);
- return new FileStream(ftp.get(path), a.getSize());
- } catch (SftpException je) {
- if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
+ return new FileStream(ftp.get(path));
+ } catch (FtpChannel.FtpException f) {
+ if (f.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
throw new FileNotFoundException(path);
+ }
throw new TransportException(MessageFormat.format(
JGitText.get().cannotGetObjectsPath, objectsPath, path,
- je.getMessage()), je);
+ f.getMessage()), f);
}
}
@Override
void deleteFile(String path) throws IOException {
try {
- ftp.rm(path);
- } catch (SftpException je) {
- if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
- return;
+ ftp.delete(path);
+ } catch (FtpChannel.FtpException f) {
throw new TransportException(MessageFormat.format(
JGitText.get().cannotDeleteObjectsPath, objectsPath,
- path, je.getMessage()), je);
+ path, f.getMessage()), f);
}
// Prune any now empty directories.
@@ -312,7 +288,7 @@ public class TransportSftp extends SshTransport implements WalkTransport {
dir = dir.substring(0, s);
ftp.rmdir(dir);
s = dir.lastIndexOf('/');
- } catch (SftpException je) {
+ } catch (IOException je) {
// If we cannot delete it, leave it alone. It may have
// entries still in it, or maybe we lack write access on
// the parent. Either way it isn't a fatal error.
@@ -323,25 +299,31 @@ public class TransportSftp extends SshTransport implements WalkTransport {
}
@Override
- OutputStream writeFile(final String path,
- final ProgressMonitor monitor, final String monitorTask)
- throws IOException {
+ OutputStream writeFile(String path, ProgressMonitor monitor,
+ String monitorTask) throws IOException {
+ Throwable err = null;
try {
return ftp.put(path);
- } catch (SftpException je) {
- if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+ } catch (FileNotFoundException e) {
+ mkdir_p(path);
+ } catch (FtpChannel.FtpException je) {
+ if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
mkdir_p(path);
- try {
- return ftp.put(path);
- } catch (SftpException je2) {
- je = je2;
- }
+ } else {
+ err = je;
}
-
- throw new TransportException(MessageFormat.format(
- JGitText.get().cannotWriteObjectsPath, objectsPath,
- path, je.getMessage()), je);
}
+ if (err == null) {
+ try {
+ return ftp.put(path);
+ } catch (IOException e) {
+ err = e;
+ }
+ }
+ throw new TransportException(
+ MessageFormat.format(JGitText.get().cannotWriteObjectsPath,
+ objectsPath, path, err.getMessage()),
+ err);
}
@Override
@@ -351,15 +333,15 @@ public class TransportSftp extends SshTransport implements WalkTransport {
super.writeFile(lock, data);
try {
ftp.rename(lock, path);
- } catch (SftpException je) {
+ } catch (IOException e) {
throw new TransportException(MessageFormat.format(
JGitText.get().cannotWriteObjectsPath, objectsPath,
- path, je.getMessage()), je);
+ path, e.getMessage()), e);
}
} catch (IOException err) {
try {
ftp.rm(lock);
- } catch (SftpException e) {
+ } catch (IOException e) {
// Ignore deletion failure, we are already
// failing anyway.
}
@@ -373,23 +355,30 @@ public class TransportSftp extends SshTransport implements WalkTransport {
return;
path = path.substring(0, s);
+ Throwable err = null;
try {
ftp.mkdir(path);
- } catch (SftpException je) {
- if (je.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
+ return;
+ } catch (FileNotFoundException f) {
+ mkdir_p(path);
+ } catch (FtpChannel.FtpException je) {
+ if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
mkdir_p(path);
- try {
- ftp.mkdir(path);
- return;
- } catch (SftpException je2) {
- je = je2;
- }
+ } else {
+ err = je;
}
-
- throw new TransportException(MessageFormat.format(
- JGitText.get().cannotMkdirObjectPath, objectsPath, path,
- je.getMessage()), je);
}
+ if (err == null) {
+ try {
+ ftp.mkdir(path);
+ return;
+ } catch (IOException e) {
+ err = e;
+ }
+ }
+ throw new TransportException(MessageFormat.format(
+ JGitText.get().cannotMkdirObjectPath, objectsPath, path,
+ err.getMessage()), err);
}
Map<String, Ref> readAdvertisedRefs() throws TransportException {
@@ -400,34 +389,33 @@ public class TransportSftp extends SshTransport implements WalkTransport {
return avail;
}
- @SuppressWarnings("unchecked")
- private void readLooseRefs(final TreeMap<String, Ref> avail,
- final String dir, final String prefix)
- throws TransportException {
- final Collection<ChannelSftp.LsEntry> list;
+ private void readLooseRefs(TreeMap<String, Ref> avail, String dir,
+ String prefix) throws TransportException {
+ final Collection<FtpChannel.DirEntry> list;
try {
list = ftp.ls(dir);
- } catch (SftpException je) {
+ } catch (IOException e) {
throw new TransportException(MessageFormat.format(
JGitText.get().cannotListObjectsPath, objectsPath, dir,
- je.getMessage()), je);
+ e.getMessage()), e);
}
- for (ChannelSftp.LsEntry ent : list) {
- final String n = ent.getFilename();
+ for (FtpChannel.DirEntry ent : list) {
+ String n = ent.getFilename();
if (".".equals(n) || "..".equals(n)) //$NON-NLS-1$ //$NON-NLS-2$
continue;
- final String nPath = dir + "/" + n; //$NON-NLS-1$
- if (ent.getAttrs().isDir())
+ String nPath = dir + "/" + n; //$NON-NLS-1$
+ if (ent.isDirectory()) {
readLooseRefs(avail, nPath, prefix + n + "/"); //$NON-NLS-1$
- else
+ } else {
readRef(avail, nPath, prefix + n);
+ }
}
}
- private Ref readRef(final TreeMap<String, Ref> avail,
- final String path, final String name) throws TransportException {
+ private Ref readRef(TreeMap<String, Ref> avail, String path,
+ String name) throws TransportException {
final String line;
try (BufferedReader br = openReader(path)) {
line = br.readLine();
@@ -439,10 +427,10 @@ public class TransportSftp extends SshTransport implements WalkTransport {
err.getMessage()), err);
}
- if (line == null)
+ if (line == null) {
throw new TransportException(
MessageFormat.format(JGitText.get().emptyRef, name));
-
+ }
if (line.startsWith("ref: ")) { //$NON-NLS-1$
final String target = line.substring("ref: ".length()); //$NON-NLS-1$
Ref r = avail.get(target);
@@ -467,8 +455,9 @@ public class TransportSftp extends SshTransport implements WalkTransport {
}
private Storage loose(Ref r) {
- if (r != null && r.getStorage() == Storage.PACKED)
+ if (r != null && r.getStorage() == Storage.PACKED) {
return Storage.LOOSE_PACKED;
+ }
return Storage.LOOSE;
}
@@ -476,8 +465,9 @@ public class TransportSftp extends SshTransport implements WalkTransport {
void close() {
if (ftp != null) {
try {
- if (ftp.isConnected())
+ if (ftp.isConnected()) {
ftp.disconnect();
+ }
} finally {
ftp = null;
}
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 48a3e0b38f..2fbcaa2928 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -49,6 +49,7 @@ import static org.eclipse.jgit.lib.Constants.R_TAGS;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_WANT;
import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_FETCH;
import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SERVER_OPTION;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
@@ -70,11 +71,11 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -88,6 +89,7 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.internal.transport.parser.FirstWant;
import org.eclipse.jgit.lib.BitmapIndex;
import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
import org.eclipse.jgit.lib.Constants;
@@ -96,6 +98,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
import org.eclipse.jgit.revwalk.BitmapWalker;
@@ -179,44 +182,53 @@ public class UploadPack {
throws PackProtocolException, IOException;
}
- /** Data in the first line of a request, the line itself plus options. */
+ /**
+ * Data in the first line of a want-list, the line itself plus options.
+ *
+ * @deprecated Use {@link FirstWant} instead
+ */
+ @Deprecated
public static class FirstLine {
- private final String line;
- private final Set<String> options;
+
+ private final FirstWant firstWant;
/**
- * Parse the first line of a receive-pack request.
- *
* @param line
* line from the client.
*/
public FirstLine(String line) {
- if (line.length() > 45) {
- final HashSet<String> opts = new HashSet<>();
- String opt = line.substring(45);
- if (opt.startsWith(" ")) //$NON-NLS-1$
- opt = opt.substring(1);
- for (String c : opt.split(" ")) //$NON-NLS-1$
- opts.add(c);
- this.line = line.substring(0, 45);
- this.options = Collections.unmodifiableSet(opts);
- } else {
- this.line = line;
- this.options = Collections.emptySet();
+ try {
+ firstWant = FirstWant.fromLine(line);
+ } catch (PackProtocolException e) {
+ throw new UncheckedIOException(e);
}
}
/** @return non-capabilities part of the line. */
public String getLine() {
- return line;
+ return firstWant.getLine();
}
- /** @return options parsed from the line. */
+ /** @return capabilities parsed from the line. */
public Set<String> getOptions() {
- return options;
+ if (firstWant.getAgent() != null) {
+ Set<String> caps = new HashSet<>(firstWant.getCapabilities());
+ caps.add(OPTION_AGENT + '=' + firstWant.getAgent());
+ return caps;
+ }
+ return firstWant.getCapabilities();
}
}
+ /*
+ * {@link java.util.function.Consumer} doesn't allow throwing checked
+ * exceptions. Define our own to propagate IOExceptions.
+ */
+ @FunctionalInterface
+ private static interface IOConsumer<R> {
+ void accept(R t) throws IOException;
+ }
+
/** Database we read the objects from. */
private final Repository db;
@@ -288,12 +300,11 @@ public class UploadPack {
/** Hook for taking post upload actions. */
private PostUploadHook postUploadHook = PostUploadHook.NULL;
- /** Capabilities requested by the client. */
- private Set<String> options;
+ /** Caller user agent */
String userAgent;
/** Raw ObjectIds the client has asked for, before validating them. */
- private final Set<ObjectId> wantIds = new HashSet<>();
+ private Set<ObjectId> wantIds = new HashSet<>();
/** Objects the client wants to obtain. */
private final Set<RevObject> wantAll = new HashSet<>();
@@ -301,25 +312,6 @@ public class UploadPack {
/** Objects on both sides, these don't have to be sent. */
private final Set<RevObject> commonBase = new HashSet<>();
- /** Shallow commits the client already has. */
- private Set<ObjectId> clientShallowCommits = new HashSet<>();
-
- /** Desired depth from the client on a shallow request. */
- private int depth;
-
- /**
- * Commit time of the newest objects the client has asked us using
- * --shallow-since not to send. Cannot be nonzero if depth is nonzero.
- */
- private int shallowSince;
-
- /**
- * (Possibly short) ref names, ancestors of which the client has asked us
- * not to send using --shallow-exclude. Cannot be non-empty if depth is
- * nonzero.
- */
- private List<String> deepenNotRefs = new ArrayList<>();
-
/** Commit time of the oldest common commit, in seconds. */
private int oldestTime;
@@ -353,7 +345,14 @@ public class UploadPack {
private PackStatistics statistics;
- private long filterBlobLimit = -1;
+ /**
+ * Request this instance is handling.
+ *
+ * We need to keep a reference to it for {@link PreUploadHook pre upload
+ * hooks}. They receive a reference this instance and invoke methods like
+ * getDepth() to get information about the request.
+ */
+ private FetchRequest currentRequest;
/**
* Create a new pack upload for an open repository.
@@ -695,10 +694,12 @@ public class UploadPack {
* read.
*/
public boolean isSideBand() throws RequestNotYetReadException {
- if (options == null)
+ if (currentRequest == null) {
throw new RequestNotYetReadException();
- return (options.contains(OPTION_SIDE_BAND)
- || options.contains(OPTION_SIDE_BAND_64K));
+ }
+ Set<String> caps = currentRequest.getClientCapabilities();
+ return caps.contains(OPTION_SIDE_BAND)
+ || caps.contains(OPTION_SIDE_BAND_64K);
}
/**
@@ -829,12 +830,10 @@ public class UploadPack {
}
if (refs == null) {
// Fast path: the advertised refs hook did not set advertised refs.
- Map<String, Ref> rs = new HashMap<>();
- for (String p : refPrefixes) {
- for (Ref r : db.getRefDatabase().getRefsByPrefix(p)) {
- rs.put(r.getName(), r);
- }
- }
+ String[] prefixes = refPrefixes.toArray(new String[0]);
+ Map<String, Ref> rs =
+ db.getRefDatabase().getRefsByPrefix(prefixes).stream()
+ .collect(toMap(Ref::getName, identity(), (a, b) -> b));
if (refFilter != RefFilter.DEFAULT) {
return refFilter.filter(rs);
}
@@ -880,12 +879,45 @@ public class UploadPack {
return getAdvertisedOrDefaultRefs().get(name);
}
+ /**
+ * Find a ref in the usual search path on behalf of the client.
+ * <p>
+ * This checks that the ref is present in the ref advertisement since
+ * otherwise the client might not be supposed to be able to read it.
+ *
+ * @param name
+ * short name of the ref to find, e.g. "master" to find
+ * "refs/heads/master".
+ * @return the requested Ref, or {@code null} if it is not visible or
+ * does not exist.
+ * @throws java.io.IOException
+ * on failure to read the ref or check it for visibility.
+ */
+ @Nullable
+ private Ref findRef(String name) throws IOException {
+ if (refs != null) {
+ return RefDatabase.findRef(refs, name);
+ }
+ if (!advertiseRefsHookCalled) {
+ advertiseRefsHook.advertiseRefs(this);
+ advertiseRefsHookCalled = true;
+ }
+ if (refs == null &&
+ refFilter == RefFilter.DEFAULT &&
+ transferConfig.hasDefaultRefFilter()) {
+ // Fast path: no ref filtering is needed.
+ return db.getRefDatabase().getRef(name);
+ }
+ return RefDatabase.findRef(getAdvertisedOrDefaultRefs(), name);
+ }
+
private void service() throws IOException {
boolean sendPack = false;
// If it's a non-bidi request, we need to read the entire request before
// writing a response. Buffer the response until then.
PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator();
List<ObjectId> unshallowCommits = new ArrayList<>();
+ FetchRequest req;
try {
if (biDirectionalPipe)
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
@@ -896,29 +928,46 @@ public class UploadPack {
long negotiateStart = System.currentTimeMillis();
accumulator.advertised = advertised.size();
- recvWants();
- if (wantIds.isEmpty()) {
- preUploadHook.onBeginNegotiateRound(this, wantIds, 0);
- preUploadHook.onEndNegotiateRound(this, wantIds, 0, 0, false);
+
+ ProtocolV0Parser parser = new ProtocolV0Parser(transferConfig);
+ req = parser.recvWants(pckIn);
+ currentRequest = req;
+
+ wantIds = req.getWantIds();
+
+ if (req.getWantIds().isEmpty()) {
+ preUploadHook.onBeginNegotiateRound(this, req.getWantIds(), 0);
+ preUploadHook.onEndNegotiateRound(this, req.getWantIds(), 0, 0,
+ false);
return;
}
- accumulator.wants = wantIds.size();
+ accumulator.wants = req.getWantIds().size();
- if (options.contains(OPTION_MULTI_ACK_DETAILED)) {
+ if (req.getClientCapabilities().contains(OPTION_MULTI_ACK_DETAILED)) {
multiAck = MultiAck.DETAILED;
- noDone = options.contains(OPTION_NO_DONE);
- } else if (options.contains(OPTION_MULTI_ACK))
+ noDone = req.getClientCapabilities().contains(OPTION_NO_DONE);
+ } else if (req.getClientCapabilities().contains(OPTION_MULTI_ACK))
multiAck = MultiAck.CONTINUE;
else
multiAck = MultiAck.OFF;
- if (!clientShallowCommits.isEmpty())
- verifyClientShallow(clientShallowCommits);
- if (depth != 0)
- processShallow(null, unshallowCommits, true);
- if (!clientShallowCommits.isEmpty())
- walk.assumeShallow(clientShallowCommits);
- sendPack = negotiate(accumulator);
+ if (!req.getClientShallowCommits().isEmpty()) {
+ verifyClientShallow(req.getClientShallowCommits());
+ }
+
+ if (req.getDepth() != 0 || req.getDeepenSince() != 0) {
+ computeShallowsAndUnshallows(req, shallow -> {
+ pckOut.writeString("shallow " + shallow.name() + '\n'); //$NON-NLS-1$
+ }, unshallow -> {
+ pckOut.writeString("unshallow " + unshallow.name() + '\n'); //$NON-NLS-1$
+ unshallowCommits.add(unshallow);
+ }, Collections.emptyList());
+ pckOut.end();
+ }
+
+ if (!req.getClientShallowCommits().isEmpty())
+ walk.assumeShallow(req.getClientShallowCommits());
+ sendPack = negotiate(req, accumulator);
accumulator.timeNegotiating += System.currentTimeMillis()
- negotiateStart;
@@ -968,35 +1017,14 @@ public class UploadPack {
}
if (sendPack) {
- sendPack(accumulator, refs == null ? null : refs.values(), unshallowCommits);
+ sendPack(accumulator, req, refs == null ? null : refs.values(),
+ unshallowCommits, Collections.emptyList());
}
}
private void lsRefsV2() throws IOException {
- LsRefsV2Request.Builder builder = LsRefsV2Request.builder();
- List<String> prefixes = new ArrayList<>();
- String line = pckIn.readString();
- // Currently, we do not support any capabilities, so the next
- // line is DELIM if there are arguments or END if not.
- if (line == PacketLineIn.DELIM) {
- while ((line = pckIn.readString()) != PacketLineIn.END) {
- if (line.equals("peel")) { //$NON-NLS-1$
- builder.setPeel(true);
- } else if (line.equals("symrefs")) { //$NON-NLS-1$
- builder.setSymrefs(true);
- } else if (line.startsWith("ref-prefix ")) { //$NON-NLS-1$
- prefixes.add(line.substring("ref-prefix ".length())); //$NON-NLS-1$
- } else {
- throw new PackProtocolException(MessageFormat
- .format(JGitText.get().unexpectedPacketLine, line));
- }
- }
- } else if (line != PacketLineIn.END) {
- throw new PackProtocolException(MessageFormat
- .format(JGitText.get().unexpectedPacketLine, line));
- }
- LsRefsV2Request req = builder.setRefPrefixes(prefixes).build();
-
+ ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
+ LsRefsV2Request req = parser.parseLsRefsRequest(pckIn);
protocolV2Hook.onLsRefs(req);
rawOut.stopBuffering();
@@ -1029,20 +1057,23 @@ public class UploadPack {
ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
FetchV2Request req = parser.parseFetchRequest(pckIn);
+ currentRequest = req;
rawOut.stopBuffering();
protocolV2Hook.onFetch(req);
// TODO(ifrade): Refactor to pass around the Request object, instead of
// copying data back to class fields
- options = req.getOptions();
- clientShallowCommits = req.getClientShallowCommits();
- depth = req.getDepth();
- shallowSince = req.getDeepenSince();
- filterBlobLimit = req.getFilterBlobLimit();
- deepenNotRefs = req.getDeepenNotRefs();
-
- wantIds.addAll(req.getWantsIds());
+ 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());
+ }
+
Map<String, ObjectId> wantedRefs = new TreeMap<>();
for (String refName : req.getWantedRefs()) {
Ref ref = getRef(refName);
@@ -1055,21 +1086,27 @@ public class UploadPack {
throw new PackProtocolException(MessageFormat
.format(JGitText.get().invalidRefName, refName));
}
- wantIds.add(oid);
+ // TODO(ifrade): Avoid mutating the parsed request.
+ req.getWantIds().add(oid);
wantedRefs.put(refName, oid);
}
+ wantIds = req.getWantIds();
boolean sectionSent = false;
- @Nullable List<ObjectId> shallowCommits = null;
+ boolean mayHaveShallow = req.getDepth() != 0
+ || req.getDeepenSince() != 0
+ || !req.getDeepenNotRefs().isEmpty();
+ List<ObjectId> shallowCommits = new ArrayList<>();
List<ObjectId> unshallowCommits = new ArrayList<>();
if (!req.getClientShallowCommits().isEmpty()) {
verifyClientShallow(req.getClientShallowCommits());
}
- if (req.getDepth() != 0 || req.getDeepenSince() != 0
- || !req.getDeepenNotRefs().isEmpty()) {
- shallowCommits = new ArrayList<>();
- processShallow(shallowCommits, unshallowCommits, false);
+ if (mayHaveShallow) {
+ computeShallowsAndUnshallows(req,
+ shallowCommit -> shallowCommits.add(shallowCommit),
+ unshallowCommit -> unshallowCommits.add(unshallowCommit),
+ deepenNots);
}
if (!req.getClientShallowCommits().isEmpty())
walk.assumeShallow(req.getClientShallowCommits());
@@ -1095,7 +1132,7 @@ public class UploadPack {
}
if (req.wasDoneReceived() || okToGiveUp()) {
- if (shallowCommits != null) {
+ if (mayHaveShallow) {
if (sectionSent)
pckOut.writeDelim();
pckOut.writeString("shallow-info\n"); //$NON-NLS-1$
@@ -1125,10 +1162,11 @@ public class UploadPack {
pckOut.writeDelim();
pckOut.writeString("packfile\n"); //$NON-NLS-1$
sendPack(new PackStatistics.Accumulator(),
- req.getOptions().contains(OPTION_INCLUDE_TAG)
+ req,
+ req.getClientCapabilities().contains(OPTION_INCLUDE_TAG)
? db.getRefDatabase().getRefsByPrefix(R_TAGS)
: null,
- unshallowCommits);
+ unshallowCommits, deepenNots);
// sendPack invokes pckOut.end() for us, so we do not
// need to invoke it here.
} else {
@@ -1179,6 +1217,7 @@ public class UploadPack {
(transferConfig.isAllowFilter() ? OPTION_FILTER + ' ' : "") + //$NON-NLS-1$
(advertiseRefInWant ? CAPABILITY_REF_IN_WANT + ' ' : "") + //$NON-NLS-1$
OPTION_SHALLOW);
+ caps.add(CAPABILITY_SERVER_OPTION);
return caps;
}
@@ -1227,28 +1266,28 @@ public class UploadPack {
}
/*
- * Determines what "shallow" and "unshallow" lines to send to the user.
- * The information is written to shallowCommits (if not null) and
- * unshallowCommits, and also written to #pckOut (if writeToPckOut is
- * true).
+ * Determines what object ids must be marked as shallow or unshallow for the
+ * client.
*/
- private void processShallow(@Nullable List<ObjectId> shallowCommits,
- List<ObjectId> unshallowCommits,
- boolean writeToPckOut) throws IOException {
- if (options.contains(OPTION_DEEPEN_RELATIVE) ||
- shallowSince != 0 ||
- !deepenNotRefs.isEmpty()) {
- // TODO(jonathantanmy): Implement deepen-relative, deepen-since,
- // and deepen-not.
+ private void computeShallowsAndUnshallows(FetchRequest req,
+ IOConsumer<ObjectId> shallowFunc,
+ IOConsumer<ObjectId> unshallowFunc,
+ List<ObjectId> deepenNots)
+ throws IOException {
+ if (req.getClientCapabilities().contains(OPTION_DEEPEN_RELATIVE)) {
+ // TODO(jonathantanmy): Implement deepen-relative
throw new UnsupportedOperationException();
}
- int walkDepth = depth - 1;
+ int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE
+ : req.getDepth() - 1;
try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk(
walk.getObjectReader(), walkDepth)) {
+ depthWalk.setDeepenSince(req.getDeepenSince());
+
// Find all the commits which will be shallow
- for (ObjectId o : wantIds) {
+ for (ObjectId o : req.getWantIds()) {
try {
depthWalk.markRoot(depthWalk.parseCommit(o));
} catch (IncorrectObjectTypeException notCommit) {
@@ -1256,35 +1295,32 @@ public class UploadPack {
}
}
+ depthWalk.setDeepenNots(deepenNots);
+
RevCommit o;
+ boolean atLeastOne = false;
while ((o = depthWalk.next()) != null) {
DepthWalk.Commit c = (DepthWalk.Commit) o;
+ atLeastOne = true;
+
+ boolean isBoundary = (c.getDepth() == walkDepth) || c.isBoundary();
// Commits at the boundary which aren't already shallow in
// the client need to be marked as such
- if (c.getDepth() == walkDepth
- && !clientShallowCommits.contains(c)) {
- if (shallowCommits != null) {
- shallowCommits.add(c.copy());
- }
- if (writeToPckOut) {
- pckOut.writeString("shallow " + o.name()); //$NON-NLS-1$
- }
+ if (isBoundary && !req.getClientShallowCommits().contains(c)) {
+ shallowFunc.accept(c.copy());
}
// Commits not on the boundary which are shallow in the client
// need to become unshallowed
- if (c.getDepth() < walkDepth
- && clientShallowCommits.remove(c)) {
- unshallowCommits.add(c.copy());
- if (writeToPckOut) {
- pckOut.writeString("unshallow " + c.name()); //$NON-NLS-1$
- }
+ if (!isBoundary && req.getClientShallowCommits().remove(c)) {
+ unshallowFunc.accept(c.copy());
}
}
- }
- if (writeToPckOut) {
- pckOut.end();
+ if (!atLeastOne) {
+ throw new PackProtocolException(
+ JGitText.get().noCommitsSelectedForShallow);
+ }
}
}
@@ -1436,67 +1472,6 @@ public class UploadPack {
return msgOut;
}
- private void recvWants() throws IOException {
- boolean isFirst = true;
- boolean filterReceived = false;
- for (;;) {
- String line;
- try {
- line = pckIn.readString();
- } catch (EOFException eof) {
- if (isFirst)
- break;
- throw eof;
- }
-
- if (line == PacketLineIn.END)
- break;
-
- if (line.startsWith("deepen ")) { //$NON-NLS-1$
- depth = Integer.parseInt(line.substring(7));
- if (depth <= 0) {
- throw new PackProtocolException(
- MessageFormat.format(JGitText.get().invalidDepth,
- Integer.valueOf(depth)));
- }
- continue;
- }
-
- if (line.startsWith("shallow ")) { //$NON-NLS-1$
- clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
- continue;
- }
-
- if (transferConfig.isAllowFilter()
- && line.startsWith(OPTION_FILTER + " ")) { //$NON-NLS-1$
- String arg = line.substring(OPTION_FILTER.length() + 1);
-
- if (filterReceived) {
- throw new PackProtocolException(JGitText.get().tooManyFilters);
- }
- filterReceived = true;
-
- filterBlobLimit = ProtocolV2Parser.filterLine(arg);
- continue;
- }
-
- if (!line.startsWith("want ") || line.length() < 45) //$NON-NLS-1$
- throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$
-
- if (isFirst) {
- if (line.length() > 45) {
- FirstLine firstLine = new FirstLine(line);
- options = firstLine.getOptions();
- line = firstLine.getLine();
- } else
- options = Collections.emptySet();
- }
-
- wantIds.add(ObjectId.fromString(line.substring(5)));
- isFirst = false;
- }
- }
-
/**
* Returns the clone/fetch depth. Valid only after calling recvWants(). A
* depth of 1 means return only the wants.
@@ -1505,9 +1480,9 @@ public class UploadPack {
* @since 4.0
*/
public int getDepth() {
- if (options == null)
+ if (currentRequest == null)
throw new RequestNotYetReadException();
- return depth;
+ return currentRequest.getDepth();
}
/**
@@ -1526,10 +1501,15 @@ public class UploadPack {
* @since 4.0
*/
public String getPeerUserAgent() {
- return UserAgent.getAgent(options, userAgent);
+ if (currentRequest != null && currentRequest.getAgent() != null) {
+ return currentRequest.getAgent();
+ }
+
+ return userAgent;
}
- private boolean negotiate(PackStatistics.Accumulator accumulator)
+ private boolean negotiate(FetchRequest req,
+ PackStatistics.Accumulator accumulator)
throws IOException {
okToGiveUp = Boolean.FALSE;
@@ -1545,7 +1525,7 @@ public class UploadPack {
// disconnected, and will try another request with actual want/have.
// Don't report the EOF here, its a bug in the protocol that the client
// just disconnects without sending an END.
- if (!biDirectionalPipe && depth > 0)
+ if (!biDirectionalPipe && req.getDepth() > 0)
return false;
throw eof;
}
@@ -1926,25 +1906,31 @@ public class UploadPack {
* Send the requested objects to the client.
*
* @param accumulator
- * where to write statistics about the content of the pack.
+ * where to write statistics about the content of the pack.
+ * @param req
+ * request in process
* @param allTags
- * refs to search for annotated tags to include in the pack
- * if the {@link #OPTION_INCLUDE_TAG} capability was
- * requested.
+ * refs to search for annotated tags to include in the pack if
+ * the {@link #OPTION_INCLUDE_TAG} capability was requested.
* @param unshallowCommits
- * shallow commits on the client that are now becoming
- * unshallow
+ * shallow commits on the client that are now becoming unshallow
+ * @param deepenNots
+ * objects that the client specified using --shallow-exclude
* @throws IOException
- * if an error occured while generating or writing the pack.
+ * if an error occurred while generating or writing the pack.
*/
private void sendPack(PackStatistics.Accumulator accumulator,
+ FetchRequest req,
@Nullable Collection<Ref> allTags,
- List<ObjectId> unshallowCommits) throws IOException {
- final boolean sideband = options.contains(OPTION_SIDE_BAND)
- || options.contains(OPTION_SIDE_BAND_64K);
+ List<ObjectId> unshallowCommits,
+ List<ObjectId> deepenNots) throws IOException {
+ Set<String> caps = req.getClientCapabilities();
+ boolean sideband = caps.contains(OPTION_SIDE_BAND)
+ || caps.contains(OPTION_SIDE_BAND_64K);
if (sideband) {
try {
- sendPack(true, accumulator, allTags, unshallowCommits);
+ sendPack(true, req, accumulator, allTags, unshallowCommits,
+ deepenNots);
} catch (ServiceMayNotContinueException noPack) {
// This was already reported on (below).
throw noPack;
@@ -1965,7 +1951,7 @@ public class UploadPack {
throw err;
}
} else {
- sendPack(false, accumulator, allTags, unshallowCommits);
+ sendPack(false, req, accumulator, allTags, unshallowCommits, deepenNots);
}
}
@@ -1989,35 +1975,39 @@ public class UploadPack {
* Send the requested objects to the client.
*
* @param sideband
- * whether to wrap the pack in side-band pkt-lines,
- * interleaved with progress messages and errors.
+ * whether to wrap the pack in side-band pkt-lines, interleaved
+ * with progress messages and errors.
+ * @param req
+ * request being processed
* @param accumulator
- * where to write statistics about the content of the pack.
+ * where to write statistics about the content of the pack.
* @param allTags
- * refs to search for annotated tags to include in the pack
- * if the {@link #OPTION_INCLUDE_TAG} capability was
- * requested.
+ * refs to search for annotated tags to include in the pack if
+ * the {@link #OPTION_INCLUDE_TAG} capability was requested.
* @param unshallowCommits
- * shallow commits on the client that are now becoming
- * unshallow
+ * shallow commits on the client that are now becoming unshallow
+ * @param deepenNots
+ * objects that the client specified using --shallow-exclude
* @throws IOException
- * if an error occured while generating or writing the pack.
+ * if an error occurred while generating or writing the pack.
*/
private void sendPack(final boolean sideband,
+ FetchRequest req,
PackStatistics.Accumulator accumulator,
@Nullable Collection<Ref> allTags,
- List<ObjectId> unshallowCommits) throws IOException {
+ List<ObjectId> unshallowCommits,
+ List<ObjectId> deepenNots) throws IOException {
ProgressMonitor pm = NullProgressMonitor.INSTANCE;
OutputStream packOut = rawOut;
if (sideband) {
int bufsz = SideBandOutputStream.SMALL_BUF;
- if (options.contains(OPTION_SIDE_BAND_64K))
+ if (req.getClientCapabilities().contains(OPTION_SIDE_BAND_64K))
bufsz = SideBandOutputStream.MAX_BUF;
packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
bufsz, rawOut);
- if (!options.contains(OPTION_NO_PROGRESS)) {
+ if (!req.getClientCapabilities().contains(OPTION_NO_PROGRESS)) {
msgOut = new SideBandOutputStream(
SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
pm = new SideBandProgressMonitor(msgOut);
@@ -2053,17 +2043,20 @@ public class UploadPack {
accumulator);
try {
pw.setIndexDisabled(true);
- if (filterBlobLimit >= 0) {
- pw.setFilterBlobLimit(filterBlobLimit);
+ if (req.getFilterBlobLimit() >= 0) {
+ pw.setFilterBlobLimit(req.getFilterBlobLimit());
pw.setUseCachedPacks(false);
} else {
pw.setUseCachedPacks(true);
}
- pw.setUseBitmaps(depth == 0 && clientShallowCommits.isEmpty());
- pw.setClientShallowCommits(clientShallowCommits);
+ pw.setUseBitmaps(
+ req.getDepth() == 0
+ && req.getClientShallowCommits().isEmpty());
+ pw.setClientShallowCommits(req.getClientShallowCommits());
pw.setReuseDeltaCommits(true);
- pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA));
- pw.setThin(options.contains(OPTION_THIN_PACK));
+ pw.setDeltaBaseAsOffset(
+ req.getClientCapabilities().contains(OPTION_OFS_DELTA));
+ pw.setThin(req.getClientCapabilities().contains(OPTION_THIN_PACK));
pw.setReuseValidatingObjects(false);
// Objects named directly by references go at the beginning
@@ -2082,14 +2075,22 @@ public class UploadPack {
}
RevWalk rw = walk;
- if (depth > 0) {
- pw.setShallowPack(depth, unshallowCommits);
- rw = new DepthWalk.RevWalk(walk.getObjectReader(), depth - 1);
- rw.assumeShallow(clientShallowCommits);
+ if (req.getDepth() > 0 || req.getDeepenSince() != 0 || !deepenNots.isEmpty()) {
+ int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE
+ : req.getDepth() - 1;
+ pw.setShallowPack(req.getDepth(), unshallowCommits);
+
+ DepthWalk.RevWalk dw = new DepthWalk.RevWalk(
+ walk.getObjectReader(), walkDepth);
+ dw.setDeepenSince(req.getDeepenSince());
+ dw.setDeepenNots(deepenNots);
+ dw.assumeShallow(req.getClientShallowCommits());
+ rw = dw;
}
if (wantAll.isEmpty()) {
- pw.preparePack(pm, wantIds, commonBase, clientShallowCommits);
+ pw.preparePack(pm, wantIds, commonBase,
+ req.getClientShallowCommits());
} else {
walk.reset();
@@ -2098,7 +2099,8 @@ public class UploadPack {
rw = ow;
}
- if (options.contains(OPTION_INCLUDE_TAG) && allTags != null) {
+ if (req.getClientCapabilities().contains(OPTION_INCLUDE_TAG)
+ && allTags != null) {
for (Ref ref : allTags) {
ObjectId objectId = ref.getObjectId();
if (objectId == null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
index d815bc354e..c38b00287b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
@@ -58,6 +58,8 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
+import org.eclipse.jgit.annotations.NonNull;
+
/**
* The interface of connections used during HTTP communication. This interface
* is that subset of the interface exposed by {@link java.net.HttpURLConnection}
@@ -69,25 +71,25 @@ public interface HttpConnection {
/**
* @see HttpURLConnection#HTTP_OK
*/
- public static final int HTTP_OK = java.net.HttpURLConnection.HTTP_OK;
+ int HTTP_OK = java.net.HttpURLConnection.HTTP_OK;
/**
* @see HttpURLConnection#HTTP_MOVED_PERM
* @since 4.7
*/
- public static final int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM;
+ int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM;
/**
* @see HttpURLConnection#HTTP_MOVED_TEMP
* @since 4.9
*/
- public static final int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+ int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP;
/**
* @see HttpURLConnection#HTTP_SEE_OTHER
* @since 4.9
*/
- public static final int HTTP_SEE_OTHER = java.net.HttpURLConnection.HTTP_SEE_OTHER;
+ int HTTP_SEE_OTHER = java.net.HttpURLConnection.HTTP_SEE_OTHER;
/**
* HTTP 1.1 additional MOVED_TEMP status code; value = 307.
@@ -95,22 +97,22 @@ public interface HttpConnection {
* @see #HTTP_MOVED_TEMP
* @since 4.9
*/
- public static final int HTTP_11_MOVED_TEMP = 307;
+ int HTTP_11_MOVED_TEMP = 307;
/**
* @see HttpURLConnection#HTTP_NOT_FOUND
*/
- public static final int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
+ int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
/**
* @see HttpURLConnection#HTTP_UNAUTHORIZED
*/
- public static final int HTTP_UNAUTHORIZED = java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+ int HTTP_UNAUTHORIZED = java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
/**
* @see HttpURLConnection#HTTP_FORBIDDEN
*/
- public static final int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN;
+ int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN;
/**
* Get response code
@@ -119,7 +121,7 @@ public interface HttpConnection {
* @return the HTTP Status-Code, or -1
* @throws java.io.IOException
*/
- public int getResponseCode() throws IOException;
+ int getResponseCode() throws IOException;
/**
* Get URL
@@ -127,7 +129,7 @@ public interface HttpConnection {
* @see HttpURLConnection#getURL()
* @return the URL.
*/
- public URL getURL();
+ URL getURL();
/**
* Get response message
@@ -136,15 +138,15 @@ public interface HttpConnection {
* @return the HTTP response message, or <code>null</code>
* @throws java.io.IOException
*/
- public String getResponseMessage() throws IOException;
+ String getResponseMessage() throws IOException;
/**
- * Get list of header fields
+ * Get map of header fields
*
* @see HttpURLConnection#getHeaderFields()
* @return a Map of header fields
*/
- public Map<String, List<String>> getHeaderFields();
+ Map<String, List<String>> getHeaderFields();
/**
* Set request property
@@ -156,7 +158,7 @@ public interface HttpConnection {
* @param value
* the value associated with it.
*/
- public void setRequestProperty(String key, String value);
+ void setRequestProperty(String key, String value);
/**
* Set request method
@@ -170,7 +172,7 @@ public interface HttpConnection {
* @throws java.net.ProtocolException
* if any.
*/
- public void setRequestMethod(String method)
+ void setRequestMethod(String method)
throws ProtocolException;
/**
@@ -181,7 +183,7 @@ public interface HttpConnection {
* a <code>boolean</code> indicating whether or not to allow
* caching
*/
- public void setUseCaches(boolean usecaches);
+ void setUseCaches(boolean usecaches);
/**
* Set connect timeout
@@ -191,7 +193,7 @@ public interface HttpConnection {
* an <code>int</code> that specifies the connect timeout value
* in milliseconds
*/
- public void setConnectTimeout(int timeout);
+ void setConnectTimeout(int timeout);
/**
* Set read timeout
@@ -201,7 +203,7 @@ public interface HttpConnection {
* an <code>int</code> that specifies the timeout value to be
* used in milliseconds
*/
- public void setReadTimeout(int timeout);
+ void setReadTimeout(int timeout);
/**
* Get content type
@@ -210,7 +212,7 @@ public interface HttpConnection {
* @return the content type of the resource that the URL references, or
* <code>null</code> if not known.
*/
- public String getContentType();
+ String getContentType();
/**
* Get input stream
@@ -222,10 +224,16 @@ public interface HttpConnection {
* @throws java.io.IOException
* if any.
*/
- public InputStream getInputStream() throws IOException;
+ InputStream getInputStream() throws IOException;
/**
- * Get header field
+ * Get header field. According to
+ * {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC
+ * 2616</a>} header field names are case insensitive. Header fields defined
+ * as a comma separated list can have multiple header fields with the same
+ * field name. This method only returns one of these header fields. If you
+ * want the union of all values of all multiple header fields with the same
+ * field name then use {@link #getHeaderFields(String)}
*
* @see HttpURLConnection#getHeaderField(String)
* @param name
@@ -233,7 +241,22 @@ public interface HttpConnection {
* @return the value of the named header field, or <code>null</code> if
* there is no such field in the header.
*/
- public String getHeaderField(String name);
+ String getHeaderField(@NonNull String name);
+
+ /**
+ * Get all values of given header field. According to
+ * {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC
+ * 2616</a>} header field names are case insensitive. Header fields defined
+ * as a comma separated list can have multiple header fields with the same
+ * field name. This method does not validate if the given header field is
+ * defined as a comma separated list.
+ *
+ * @param name
+ * the name of a header field.
+ * @return the list of values of the named header field
+ * @since 5.2
+ */
+ List<String> getHeaderFields(@NonNull String name);
/**
* Get content length
@@ -243,7 +266,7 @@ public interface HttpConnection {
* references, {@code -1} if the content length is not known, or if
* the content length is greater than Integer.MAX_VALUE.
*/
- public int getContentLength();
+ int getContentLength();
/**
* Set whether or not to follow HTTP redirects.
@@ -253,7 +276,7 @@ public interface HttpConnection {
* a <code>boolean</code> indicating whether or not to follow
* HTTP redirects.
*/
- public void setInstanceFollowRedirects(boolean followRedirects);
+ void setInstanceFollowRedirects(boolean followRedirects);
/**
* Set if to do output
@@ -262,7 +285,7 @@ public interface HttpConnection {
* @param dooutput
* the new value.
*/
- public void setDoOutput(boolean dooutput);
+ void setDoOutput(boolean dooutput);
/**
* Set fixed length streaming mode
@@ -271,7 +294,7 @@ public interface HttpConnection {
* @param contentLength
* The number of bytes which will be written to the OutputStream.
*/
- public void setFixedLengthStreamingMode(int contentLength);
+ void setFixedLengthStreamingMode(int contentLength);
/**
* Get output stream
@@ -280,7 +303,7 @@ public interface HttpConnection {
* @return an output stream that writes to this connection.
* @throws java.io.IOException
*/
- public OutputStream getOutputStream() throws IOException;
+ OutputStream getOutputStream() throws IOException;
/**
* Set chunked streaming mode
@@ -290,7 +313,7 @@ public interface HttpConnection {
* The number of bytes to write in each chunk. If chunklen is
* less than or equal to zero, a default value will be used.
*/
- public void setChunkedStreamingMode(int chunklen);
+ void setChunkedStreamingMode(int chunklen);
/**
* Get request method
@@ -298,7 +321,7 @@ public interface HttpConnection {
* @see HttpURLConnection#getRequestMethod()
* @return the HTTP request method
*/
- public String getRequestMethod();
+ String getRequestMethod();
/**
* Whether we use a proxy
@@ -306,7 +329,7 @@ public interface HttpConnection {
* @see HttpURLConnection#usingProxy()
* @return a boolean indicating if the connection is using a proxy.
*/
- public boolean usingProxy();
+ boolean usingProxy();
/**
* Connect
@@ -314,7 +337,7 @@ public interface HttpConnection {
* @see HttpURLConnection#connect()
* @throws java.io.IOException
*/
- public void connect() throws IOException;
+ void connect() throws IOException;
/**
* Configure the connection so that it can be used for https communication.
@@ -332,7 +355,7 @@ public interface HttpConnection {
* @throws java.security.NoSuchAlgorithmException
* @throws java.security.KeyManagementException
*/
- public void configure(KeyManager[] km, TrustManager[] tm,
+ void configure(KeyManager[] km, TrustManager[] tm,
SecureRandom random) throws NoSuchAlgorithmException,
KeyManagementException;
@@ -345,6 +368,6 @@ public interface HttpConnection {
* @throws java.security.NoSuchAlgorithmException
* @throws java.security.KeyManagementException
*/
- public void setHostnameVerifier(HostnameVerifier hostnameverifier)
+ void setHostnameVerifier(HostnameVerifier hostnameverifier)
throws NoSuchAlgorithmException, KeyManagementException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
index bd9d61fe66..11691451f2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
@@ -62,7 +62,7 @@ public interface HttpConnectionFactory {
* @return a {@link org.eclipse.jgit.transport.http.HttpConnection}
* @throws java.io.IOException
*/
- public HttpConnection create(URL url) throws IOException;
+ HttpConnection create(URL url) throws IOException;
/**
* Creates a new connection to a destination defined by a
@@ -75,6 +75,6 @@ public interface HttpConnectionFactory {
* @return a {@link org.eclipse.jgit.transport.http.HttpConnection}
* @throws java.io.IOException
*/
- public HttpConnection create(URL url, Proxy proxy)
+ HttpConnection create(URL url, Proxy proxy)
throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
index 8241c59d2b..734b549294 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
@@ -53,6 +53,7 @@ import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -62,6 +63,7 @@ import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
+import org.eclipse.jgit.annotations.NonNull;
/**
* A {@link org.eclipse.jgit.transport.http.HttpConnection} which simply
* delegates every call to a {@link java.net.HttpURLConnection}. This is the
@@ -72,6 +74,11 @@ import javax.net.ssl.TrustManager;
public class JDKHttpConnection implements HttpConnection {
HttpURLConnection wrappedUrlConnection;
+ // used for mock testing
+ JDKHttpConnection(HttpURLConnection urlConnection) {
+ this.wrappedUrlConnection = urlConnection;
+ }
+
/**
* Constructor for JDKHttpConnection.
*
@@ -170,10 +177,26 @@ public class JDKHttpConnection implements HttpConnection {
/** {@inheritDoc} */
@Override
- public String getHeaderField(String name) {
+ public String getHeaderField(@NonNull String name) {
return wrappedUrlConnection.getHeaderField(name);
}
+ @Override
+ public List<String> getHeaderFields(@NonNull String name) {
+ Map<String, List<String>> m = wrappedUrlConnection.getHeaderFields();
+ List<String> fields = mapValuesToListIgnoreCase(name, m);
+ return fields;
+ }
+
+ private static List<String> mapValuesToListIgnoreCase(String keyName,
+ Map<String, List<String>> m) {
+ List<String> fields = new LinkedList<>();
+ m.entrySet().stream().filter(e -> keyName.equalsIgnoreCase(e.getKey()))
+ .filter(e -> e.getValue() != null)
+ .forEach(e -> fields.addAll(e.getValue()));
+ return fields;
+ }
+
/** {@inheritDoc} */
@Override
public int getContentLength() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
index 4967169776..b850d1ef94 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ReceivePackFactory.java
@@ -57,7 +57,7 @@ public interface ReceivePackFactory<C> {
/**
* A factory disabling the ReceivePack service for all repositories
*/
- public static final ReceivePackFactory<?> DISABLED = new ReceivePackFactory<Object>() {
+ ReceivePackFactory<?> DISABLED = new ReceivePackFactory<Object>() {
@Override
public ReceivePack create(Object req, Repository db)
throws ServiceNotEnabledException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
index a305e4cea3..4816f21bcc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/RepositoryResolver.java
@@ -57,7 +57,7 @@ public interface RepositoryResolver<C> {
/**
* Resolver configured to open nothing.
*/
- public static final RepositoryResolver<?> NONE = new RepositoryResolver<Object>() {
+ RepositoryResolver<?> NONE = new RepositoryResolver<Object>() {
@Override
public Repository open(Object req, String name)
throws RepositoryNotFoundException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
index 40d1ffdc56..bb43b136d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/UploadPackFactory.java
@@ -57,7 +57,7 @@ public interface UploadPackFactory<C> {
/**
* A factory disabling the UploadPack service for all repositories.
*/
- public static final UploadPackFactory<?> DISABLED = new UploadPackFactory<Object>() {
+ UploadPackFactory<?> DISABLED = new UploadPackFactory<Object>() {
@Override
public UploadPack create(Object req, Repository db)
throws ServiceNotEnabledException {
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 299f07fb09..ddf916f41f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -1056,7 +1056,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
if (FileMode.GITLINK == iMode
- && FileMode.TREE == wtMode) {
+ && FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
return iMode;
}
if (FileMode.TREE == iMode
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 7d37cfa659..a9cef59636 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -1703,6 +1703,13 @@ public abstract class FS {
hookPath);
ProcessBuilder hookProcess = runInShell(cmd, args);
hookProcess.directory(runDirectory);
+ Map<String, String> environment = hookProcess.environment();
+ environment.put(Constants.GIT_DIR_KEY,
+ repository.getDirectory().getAbsolutePath());
+ if (!repository.isBare()) {
+ environment.put(Constants.GIT_WORK_TREE_KEY,
+ repository.getWorkTree().getAbsolutePath());
+ }
try {
return new ProcessResult(runProcess(hookProcess, outRedirect,
errRedirect, stdinArgs), Status.OK);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
index 6d60ef3f4d..96636b7994 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
@@ -152,7 +152,8 @@ public class LfsFactory {
* @param outputStream
* @return a {@link PrePushHook} implementation or <code>null</code>
*/
- public @Nullable PrePushHook getPrePushHook(Repository repo,
+ @Nullable
+ public PrePushHook getPrePushHook(Repository repo,
PrintStream outputStream) {
return null;
}
@@ -163,7 +164,8 @@ public class LfsFactory {
*
* @return a command to install LFS support.
*/
- public @Nullable LfsInstallCommand getInstallCommand() {
+ @Nullable
+ public LfsInstallCommand getInstallCommand() {
return null;
}
@@ -294,6 +296,11 @@ public class LfsFactory {
return stream.read();
}
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ return stream.read(b, off, len);
+ }
+
/**
* @return the length of the stream
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index 28f406a49e..a440cb275c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -677,10 +677,6 @@ public final class RawParseUtils {
* <p>
* The last element (index <code>map.size()-1</code>) always contains
* <code>end</code>.
- * <p>
- * If the data contains a '\0' anywhere, the whole region is considered
- * binary and a LineMap corresponding to a single line is returned.
- * </p>
*
* @param buf
* buffer to scan.
@@ -689,18 +685,15 @@ public final class RawParseUtils {
* line 1.
* @param end
* 1 past the end of the content within <code>buf</code>.
- * @return a line map indicating the starting position of each line, or a
- * map representing the entire buffer as a single line if
- * <code>buf</code> contains a NUL byte.
+ * @return a line map indicating the starting position of each line.
*/
public static final IntList lineMap(byte[] buf, int ptr, int end) {
- IntList map = lineMapOrNull(buf, ptr, end);
- if (map == null) {
- map = new IntList(3);
- map.add(Integer.MIN_VALUE);
+ IntList map = new IntList((end - ptr) / 36);
+ map.fillTo(1, Integer.MIN_VALUE);
+ for (; ptr < end; ptr = nextLF(buf, ptr)) {
map.add(ptr);
- map.add(end);
}
+ map.add(end);
return map;
}
@@ -729,7 +722,8 @@ public final class RawParseUtils {
return map;
}
- private static @Nullable IntList lineMapOrNull(byte[] buf, int ptr, int end) {
+ @Nullable
+ private static IntList lineMapOrNull(byte[] buf, int ptr, int end) {
// Experimentally derived from multiple source repositories
// the average number of bytes/line is 36. Its a rough guess
// to initially size our map close to the target.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
index 822961f8de..d7c6bec219 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
@@ -49,6 +50,7 @@ import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
+import org.eclipse.jgit.util.SystemReader;
/**
* Utility used to create input and output stream wrappers for
@@ -57,7 +59,6 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions;
* @since 4.3
*/
public final class EolStreamTypeUtil {
- private static final boolean FORCE_EOL_LF_ON_CHECKOUT = false;
private EolStreamTypeUtil() {
}
@@ -164,11 +165,11 @@ public final class EolStreamTypeUtil {
// old git system
if (attrs.isSet("crlf")) {//$NON-NLS-1$
- return EolStreamType.TEXT_LF;
+ return EolStreamType.TEXT_LF; // Same as isSet("text")
} else if (attrs.isUnset("crlf")) {//$NON-NLS-1$
- return EolStreamType.DIRECT;
+ return EolStreamType.DIRECT; // Same as isUnset("text")
} else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$
- return EolStreamType.TEXT_LF;
+ return EolStreamType.TEXT_LF; // Same as eol=lf
}
// new git system
@@ -196,6 +197,28 @@ public final class EolStreamTypeUtil {
return EolStreamType.DIRECT;
}
+ private static EolStreamType getOutputFormat(WorkingTreeOptions options) {
+ switch (options.getAutoCRLF()) {
+ case TRUE:
+ return EolStreamType.TEXT_CRLF;
+ default:
+ // no decision
+ }
+ switch (options.getEOL()) {
+ case CRLF:
+ return EolStreamType.TEXT_CRLF;
+ case NATIVE:
+ if (SystemReader.getInstance().isWindows()) {
+ return EolStreamType.TEXT_CRLF;
+ }
+ return EolStreamType.TEXT_LF;
+ case LF:
+ default:
+ break;
+ }
+ return EolStreamType.DIRECT;
+ }
+
private static EolStreamType checkOutStreamType(WorkingTreeOptions options,
Attributes attrs) {
if (attrs.isUnset("text")) {//$NON-NLS-1$
@@ -205,57 +228,35 @@ public final class EolStreamTypeUtil {
// old git system
if (attrs.isSet("crlf")) {//$NON-NLS-1$
- return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
- : EolStreamType.DIRECT;
+ return getOutputFormat(options); // Same as isSet("text")
} else if (attrs.isUnset("crlf")) {//$NON-NLS-1$
- return EolStreamType.DIRECT;
+ return EolStreamType.DIRECT; // Same as isUnset("text")
} else if ("input".equals(attrs.getValue("crlf"))) {//$NON-NLS-1$ //$NON-NLS-2$
- return EolStreamType.DIRECT;
+ return EolStreamType.DIRECT; // Same as eol=lf
}
// new git system
String eol = attrs.getValue("eol"); //$NON-NLS-1$
- if (eol != null && "crlf".equals(eol)) //$NON-NLS-1$
- return EolStreamType.TEXT_CRLF;
- if (eol != null && "lf".equals(eol)) //$NON-NLS-1$
- return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
- : EolStreamType.DIRECT;
-
- if (attrs.isSet("text")) { //$NON-NLS-1$
- switch (options.getAutoCRLF()) {
- case TRUE:
- return EolStreamType.TEXT_CRLF;
- default:
- // no decision
- }
- switch (options.getEOL()) {
- case CRLF:
+ if (eol != null) {
+ if ("crlf".equals(eol)) {//$NON-NLS-1$
return EolStreamType.TEXT_CRLF;
- case LF:
- return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
- : EolStreamType.DIRECT;
- case NATIVE:
- default:
+ } else if ("lf".equals(eol)) { //$NON-NLS-1$
return EolStreamType.DIRECT;
}
}
+ if (attrs.isSet("text")) { //$NON-NLS-1$
+ return getOutputFormat(options);
+ }
if ("auto".equals(attrs.getValue("text"))) { //$NON-NLS-1$ //$NON-NLS-2$
- switch (options.getAutoCRLF()) {
- case TRUE:
+ EolStreamType basic = getOutputFormat(options);
+ switch (basic) {
+ case TEXT_CRLF:
return EolStreamType.AUTO_CRLF;
+ case TEXT_LF:
+ return EolStreamType.AUTO_LF;
default:
- // no decision
- }
- switch (options.getEOL()) {
- case CRLF:
- return EolStreamType.AUTO_CRLF;
- case LF:
- return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
- : EolStreamType.DIRECT;
- case NATIVE:
- default:
- return EolStreamType.DIRECT;
+ return basic;
}
}