diff options
Diffstat (limited to 'org.eclipse.jgit')
93 files changed, 2987 insertions, 735 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index cfc68662f0..bb37dd8c8a 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -3,8 +3,8 @@ <resource path="META-INF/MANIFEST.MF"> <filter id="924844039"> <message_arguments> - <message_argument value="5.2.3"/> - <message_argument value="5.2.0"/> + <message_argument value="5.3.3"/> + <message_argument value="5.3.0"/> </message_arguments> </filter> </resource> @@ -118,20 +118,6 @@ </message_arguments> </filter> </resource> - <resource path="src/org/eclipse/jgit/transport/TransferConfig.java" type="org.eclipse.jgit.transport.TransferConfig"> - <filter id="1159725059"> - <message_arguments> - <message_argument value="5.1.4"/> - <message_argument value="TransferConfig(Config)"/> - </message_arguments> - </filter> - <filter id="1159725059"> - <message_arguments> - <message_argument value="5.1.4"/> - <message_argument value="TransferConfig(Repository)"/> - </message_arguments> - </filter> - </resource> <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator"> <filter id="1142947843"> <message_arguments> @@ -157,12 +143,6 @@ <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS"> <filter id="1142947843"> <message_arguments> - <message_argument value="4.5.6"/> - <message_argument value="fileAttributes(File)"/> - </message_arguments> - </filter> - <filter id="1142947843"> - <message_arguments> <message_argument value="5.1.9"/> <message_argument value="getFileStoreAttributes(Path)"/> </message_arguments> diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD index 6ba7796b7e..b67bfac5b6 100644 --- a/org.eclipse.jgit/BUILD +++ b/org.eclipse.jgit/BUILD @@ -22,6 +22,9 @@ java_library( resources = RESOURCES, deps = [ ":insecure_cipher_factory", + "//lib:bcpg", + "//lib:bcpkix", + "//lib:bcprov", "//lib:javaewah", "//lib:jsch", "//lib:jzlib", diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 66701b0b38..d4226925bd 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.2.3.qualifier +Bundle-Version: 5.3.3.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy -Export-Package: org.eclipse.jgit.annotations;version="5.2.3", - org.eclipse.jgit.api;version="5.2.3"; +Export-Package: org.eclipse.jgit.annotations;version="5.3.3", + org.eclipse.jgit.api;version="5.3.3"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, @@ -22,53 +22,53 @@ Export-Package: org.eclipse.jgit.annotations;version="5.2.3", org.eclipse.jgit.submodule, org.eclipse.jgit.transport, org.eclipse.jgit.merge", - 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"; + org.eclipse.jgit.api.errors;version="5.3.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors", + org.eclipse.jgit.attributes;version="5.3.3", + org.eclipse.jgit.blame;version="5.3.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.2.3"; + org.eclipse.jgit.diff;version="5.3.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.2.3"; + org.eclipse.jgit.dircache;version="5.3.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.2.3"; + org.eclipse.jgit.errors;version="5.3.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.2.3";uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.fnmatch;version="5.2.3", - org.eclipse.jgit.gitrepo;version="5.2.3"; + org.eclipse.jgit.events;version="5.3.3";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.fnmatch;version="5.3.3", + org.eclipse.jgit.gitrepo;version="5.3.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.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"; + org.eclipse.jgit.gitrepo.internal;version="5.3.3";x-internal:=true, + org.eclipse.jgit.hooks;version="5.3.3";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.ignore;version="5.3.3", + org.eclipse.jgit.ignore.internal;version="5.3.3";x-friends:="org.eclipse.jgit.test", + org.eclipse.jgit.internal;version="5.3.3";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", + org.eclipse.jgit.internal.fsck;version="5.3.3";x-friends:="org.eclipse.jgit.test", + org.eclipse.jgit.internal.ketch;version="5.3.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.revwalk;version="5.3.3";x-internal:=true, + org.eclipse.jgit.internal.storage.dfs;version="5.3.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.2.3"; + org.eclipse.jgit.internal.storage.file;version="5.3.3"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.junit, org.eclipse.jgit.junit.http, @@ -77,18 +77,18 @@ Export-Package: org.eclipse.jgit.annotations;version="5.2.3", org.eclipse.jgit.pgm, 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"; + org.eclipse.jgit.internal.storage.io;version="5.3.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.pack;version="5.3.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.reftable;version="5.3.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"; + org.eclipse.jgit.internal.storage.reftree;version="5.3.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.submodule;version="5.3.3";x-internal:=true, + org.eclipse.jgit.internal.transport.parser;version="5.3.3";x-friends:="org.eclipse.jgit.http.server,org.eclipse.jgit.test", + org.eclipse.jgit.internal.transport.ssh;version="5.3.3";x-friends:="org.eclipse.jgit.ssh.apache", + org.eclipse.jgit.lib;version="5.3.3"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, @@ -98,33 +98,33 @@ Export-Package: org.eclipse.jgit.annotations;version="5.2.3", org.eclipse.jgit.treewalk, org.eclipse.jgit.transport, org.eclipse.jgit.submodule", - org.eclipse.jgit.lib.internal;version="5.2.3";x-internal:=true, - org.eclipse.jgit.merge;version="5.2.3"; + org.eclipse.jgit.lib.internal;version="5.3.3";x-internal:=true, + org.eclipse.jgit.merge;version="5.3.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.2.3", - org.eclipse.jgit.notes;version="5.2.3"; + org.eclipse.jgit.nls;version="5.3.3", + org.eclipse.jgit.notes;version="5.3.3"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.merge", - 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"; + org.eclipse.jgit.patch;version="5.3.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff", + org.eclipse.jgit.revplot;version="5.3.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk", + org.eclipse.jgit.revwalk;version="5.3.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.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"; + org.eclipse.jgit.revwalk.filter;version="5.3.3";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.file;version="5.3.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.pack;version="5.3.3";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.submodule;version="5.3.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk", + org.eclipse.jgit.transport;version="5.3.3"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.revwalk, org.eclipse.jgit.internal.storage.pack, @@ -132,33 +132,44 @@ Export-Package: org.eclipse.jgit.annotations;version="5.2.3", org.eclipse.jgit.util, org.eclipse.jgit.util.io, org.eclipse.jgit.internal.storage.file, + org.eclipse.jgit.internal.transport.parser, org.eclipse.jgit.lib, org.eclipse.jgit.transport.http, org.eclipse.jgit.errors, org.eclipse.jgit.storage.pack", - 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"; + org.eclipse.jgit.transport.http;version="5.3.3";uses:="javax.net.ssl", + org.eclipse.jgit.transport.resolver;version="5.3.3";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport", + org.eclipse.jgit.treewalk;version="5.3.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.2.3";uses:="org.eclipse.jgit.treewalk", - org.eclipse.jgit.util;version="5.2.3"; + org.eclipse.jgit.treewalk.filter;version="5.3.3";uses:="org.eclipse.jgit.treewalk", + org.eclipse.jgit.util;version="5.3.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.2.3", - org.eclipse.jgit.util.sha1;version="5.2.3", - org.eclipse.jgit.util.time;version="5.2.3" + org.eclipse.jgit.util.io;version="5.3.3", + org.eclipse.jgit.util.sha1;version="5.3.3", + org.eclipse.jgit.util.time;version="5.3.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)", javax.crypto, javax.net.ssl, + org.bouncycastle;version="[1.60.0,2.0.0)", + org.bouncycastle.bcpg;version="[1.60.0,2.0.0)", + org.bouncycastle.gpg;version="[1.60.0,2.0.0)", + org.bouncycastle.gpg.keybox;version="[1.60.0,2.0.0)", + org.bouncycastle.jce.provider;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp.jcajce;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp.operator;version="[1.60.0,2.0.0)", + org.bouncycastle.openpgp.operator.jcajce;version="[1.60.0,2.0.0)", + org.bouncycastle.util.encoders;version="[1.60.0,2.0.0)", org.slf4j;version="[1.7.0,2.0.0)", org.xml.sax, org.xml.sax.helpers diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF index 8e22086cce..e3fd47d5c7 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.2.3.qualifier -Eclipse-SourceBundle: org.eclipse.jgit;version="5.2.3.qualifier";roots="." +Bundle-Version: 5.3.3.qualifier +Eclipse-SourceBundle: org.eclipse.jgit;version="5.3.3.qualifier";roots="." diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml index c30d648c69..6097858185 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.2.3-SNAPSHOT</version> + <version>5.3.3-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit</artifactId> @@ -84,12 +84,26 @@ <artifactId>JavaEWAH</artifactId> </dependency> - <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpg-jdk15on</artifactId> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + </dependency> + </dependencies> <build> 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 9ab03c251c..91136620c4 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -225,6 +225,7 @@ createBranchUnexpectedResult=Create branch returned unexpected result {0} createNewFileFailed=Could not create new file {0} createRequiresZeroOldId=Create requires old ID to be zero credentialPassword=Password +credentialPassphrase=Passphrase credentialUsername=Username daemonAlreadyRunning=Daemon already running daysAgo={0} days ago @@ -326,6 +327,14 @@ gcFailed=Garbage collection failed. gcLogExists=A previous GC run reported an error: ''{0}''. Automatic gc will fail until ''{1}'' is removed. gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. Consider adjusting gc.auto or gc.pruneExpire. gitmodulesNotFound=.gitmodules not found in tree. +gpgFailedToParseSecretKey=Failed to parse secret key file in directory: {0}. Is the entered passphrase correct? +gpgNoCredentialsProvider=missing credentials provider +gpgNoKeyring=neither pubring.kbx nor secring.gpg files found +gpgNoKeyInLegacySecring=no matching secret key found in legacy secring.gpg for key or user id: {0} +gpgNoPublicKeyFound=Unable to find a public-key with key or user id: {0} +gpgNoSecretKeyForPublicKey=unable to find associated secret key for public key: {0} +gpgKeyInfo=GPG Key (fingerprint {0}) +gpgSigningCancelled=Signing was cancelled headRequiredToStash=HEAD required to stash local changes hoursAgo={0} hours ago httpConfigCannotNormalizeURL=Cannot normalize URL path {0}: too many .. segments @@ -510,6 +519,7 @@ oldIdMustNotBeNull=Expected old ID must not be null onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available onlyOneFetchSupported=Only one fetch supported onlyOneOperationCallPerConnectionIsSupported=Only one operation call per connection is supported. +onlyOpenPgpSupportedForSigning=OpenPGP is the only supported signing option with JGit at this time (gpg.format must be set to openpgp). openFilesMustBeAtLeast1=Open files must be >= 1 openingConnection=Opening connection operationCanceled=Operation {0} was canceled @@ -588,8 +598,9 @@ remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push remoteHungUpUnexpectedly=remote hung up unexpectedly -remoteNameCantBeNull=Remote name can't be null. -renameBranchFailedBecauseTag=Can not rename as Ref {0} is a tag +remoteNameCannotBeNull=Remote name cannot be null. +renameBranchFailedAmbiguous=Cannot rename branch {0}; name is ambiguous: {1} or {2} +renameBranchFailedNotABranch=Cannot rename {0}: this is not a branch renameBranchFailedUnknownReason=Rename failed with unknown reason renameBranchUnexpectedResult=Unexpected rename result {0} renameCancelled=Rename detection was cancelled @@ -629,7 +640,7 @@ selectingCommits=Selecting commits sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm. serviceNotEnabledNoName=Service not enabled serviceNotPermitted={1} not permitted on ''{0}'' -sha1CollisionDetected1=SHA-1 collision detected on {0} +sha1CollisionDetected=SHA-1 collision detected on {0} shallowCommitsAlreadyInitialized=Shallow commits have already been initialized shallowPacksRequireDepthWalk=Shallow packs require a DepthWalk shortCompressedStreamAt=Short compressed stream at {0} @@ -726,6 +737,7 @@ unableToReadPackfile=Unable to read packfile {0} unableToRemovePath=Unable to remove path ''{0}'' unableToStore=Unable to store {0}. unableToWrite=Unable to write {0} +unableToSignCommitNoSecretKey=Unable to sign commit. Signing key not available. unauthorized=Unauthorized underflowedReftableBlock=Underflowed reftable block unencodeableFile=Unencodable file: {0} 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 455a2e665f..e05f6f1bd6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -162,7 +162,9 @@ public class CheckoutCommand extends GitCommand<Ref> { private String name; - private boolean force = false; + private boolean forceRefUpdate = false; + + private boolean forced = false; private boolean createBranch = false; @@ -269,7 +271,11 @@ public class CheckoutCommand extends GitCommand<Ref> { try { dco = new DirCacheCheckout(repo, headTree, dc, newCommit.getTree()); - dco.setFailOnConflict(!force); + dco.setFailOnConflict(true); + dco.setForce(forced); + if (forced) { + dco.setFailOnConflict(false); + } dco.setProgressMonitor(monitor); try { dco.checkout(); @@ -286,7 +292,7 @@ public class CheckoutCommand extends GitCommand<Ref> { ref = null; String toName = Repository.shortenRefName(name); RefUpdate refUpdate = repo.updateRef(Constants.HEAD, ref == null); - refUpdate.setForceUpdate(force); + refUpdate.setForceUpdate(forceRefUpdate); refUpdate.setRefLogMessage(refLogMessage + " to " + toName, false); //$NON-NLS-1$ Result updateResult; if (ref != null) @@ -666,10 +672,54 @@ public class CheckoutCommand extends GitCommand<Ref> { * set to a new start-point; if false, the existing branch will * not be changed * @return this instance + * @deprecated this method was badly named comparing its semantics to native + * git's checkout --force option, use + * {@link #setForceRefUpdate(boolean)} instead */ + @Deprecated public CheckoutCommand setForce(boolean force) { + return setForceRefUpdate(force); + } + + /** + * Specify to force the ref update in case of a branch switch. + * + * In releases prior to 5.2 this method was called setForce() but this name + * was misunderstood to implement native git's --force option, which is not + * true. + * + * @param forceRefUpdate + * if <code>true</code> and the branch with the given name + * already exists, the start-point of an existing branch will be + * set to a new start-point; if false, the existing branch will + * not be changed + * @return this instance + * @since 5.3 + */ + public CheckoutCommand setForceRefUpdate(boolean forceRefUpdate) { + checkCallable(); + this.forceRefUpdate = forceRefUpdate; + return this; + } + + /** + * Allow a checkout even if the workingtree or index differs from HEAD. This + * matches native git's '--force' option. + * + * JGit releases before 5.2 had a method <code>setForce()</code> offering + * semantics different from this new <code>setForced()</code>. This old + * semantic can now be found in {@link #setForceRefUpdate(boolean)} + * + * @param forced + * if set to <code>true</code> then allow the checkout even if + * workingtree or index doesn't match HEAD. Overwrite workingtree + * files and index content with the new content in this case. + * @return this instance + * @since 5.3 + */ + public CheckoutCommand setForced(boolean forced) { checkCallable(); - this.force = force; + this.forced = forced; return this; } 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 73af8ba16d..0248ba2793 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -303,18 +303,25 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { } private List<RefSpec> calculateRefSpecs(boolean fetchAll, String dst) { - RefSpec wcrs = new RefSpec(); - wcrs = wcrs.setForceUpdate(true); - wcrs = wcrs.setSourceDestination(Constants.R_HEADS + '*', dst); + RefSpec heads = new RefSpec(); + heads = heads.setForceUpdate(true); + heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst); List<RefSpec> specs = new ArrayList<>(); if (!fetchAll) { + RefSpec tags = new RefSpec(); + tags = tags.setForceUpdate(true); + tags = tags.setSourceDestination(Constants.R_TAGS + '*', + Constants.R_TAGS + '*'); for (String selectedRef : branchesToClone) { - if (wcrs.matchSource(selectedRef)) { - specs.add(wcrs.expandFromSource(selectedRef)); + if (heads.matchSource(selectedRef)) { + specs.add(heads.expandFromSource(selectedRef)); + } else if (tags.matchSource(selectedRef)) { + specs.add(tags.expandFromSource(selectedRef)); } } } else { - specs.add(wcrs); + // We'll fetch the tags anyway. + specs.add(heads); } return specs; } @@ -590,11 +597,15 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { } /** - * Set whether all branches have to be fetched + * Set whether all branches have to be fetched. + * <p> + * If {@code false}, use {@link #setBranchesToClone(Collection)} to define + * what will be cloned. If neither are set, all branches will be cloned. + * </p> * * @param cloneAllBranches - * true when all branches have to be fetched (indicates wildcard - * in created fetch refspec), false otherwise. + * {@code true} when all branches have to be fetched (indicates + * wildcard in created fetch refspec), {@code false} otherwise. * @return {@code this} */ public CloneCommand setCloneAllBranches(boolean cloneAllBranches) { @@ -616,12 +627,17 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { } /** - * Set branches to clone + * Set the branches or tags to clone. + * <p> + * This is ignored if {@link #setCloneAllBranches(boolean) + * setCloneAllBranches(true)} is used. If {@code branchesToClone} is + * {@code null} or empty, it's also ignored and all branches will be cloned. + * </p> * * @param branchesToClone - * collection of branches to clone. Ignored when allSelected is - * true. Must be specified as full ref names (e.g. - * <code>refs/heads/master</code>). + * collection of branches to clone. Must be specified as full ref + * names (e.g. {@code refs/heads/master} or + * {@code refs/tags/v1.0.0}). * @return {@code this} */ public CloneCommand setBranchesToClone(Collection<String> branchesToClone) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index cea28fac18..61d51cc913 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -61,6 +61,7 @@ import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.UnmergedPathsException; +import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuildIterator; @@ -76,6 +77,9 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgConfig.GpgFormat; +import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; @@ -84,10 +88,12 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; +import org.eclipse.jgit.lib.internal.BouncyCastleGpgSigner; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; @@ -139,6 +145,14 @@ public class CommitCommand extends GitCommand<RevCommit> { private Boolean allowEmpty; + private Boolean signCommit; + + private String signingKey; + + private GpgSigner gpgSigner; + + private CredentialsProvider credentialsProvider; + /** * Constructor for CommitCommand * @@ -147,6 +161,7 @@ public class CommitCommand extends GitCommand<RevCommit> { */ protected CommitCommand(Repository repo) { super(repo); + this.credentialsProvider = CredentialsProvider.getDefault(); } /** @@ -251,6 +266,12 @@ public class CommitCommand extends GitCommand<RevCommit> { commit.setParentIds(parents); commit.setTreeId(indexTreeId); + + if (signCommit.booleanValue()) { + gpgSigner.sign(commit, signingKey, committer, + credentialsProvider); + } + ObjectId commitId = odi.insert(commit); odi.flush(); @@ -517,9 +538,10 @@ public class CommitCommand extends GitCommand<RevCommit> { * * @throws NoMessageException * if the commit message has not been specified + * @throws UnsupportedSigningFormatException if the configured gpg.format is not supported */ private void processOptions(RepositoryState state, RevWalk rw) - throws NoMessageException { + throws NoMessageException, UnsupportedSigningFormatException { if (committer == null) committer = new PersonIdent(repo); if (author == null && !amend) @@ -572,6 +594,25 @@ public class CommitCommand extends GitCommand<RevCommit> { // as long as we don't support -C option we have to have // an explicit message throw new NoMessageException(JGitText.get().commitMessageNotSpecified); + + GpgConfig gpgConfig = new GpgConfig(repo.getConfig()); + if (signCommit == null) { + signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE + : Boolean.FALSE; + } + if (signingKey == null) { + signingKey = gpgConfig.getSigningKey(); + } + if (gpgSigner == null) { + if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) { + throw new UnsupportedSigningFormatException( + JGitText.get().onlyOpenPgpSupportedForSigning); + } + gpgSigner = GpgSigner.getDefault(); + if (gpgSigner == null) { + gpgSigner = new BouncyCastleGpgSigner(); + } + } } private boolean isMergeDuringRebase(RepositoryState state) { @@ -873,4 +914,55 @@ public class CommitCommand extends GitCommand<RevCommit> { hookOutRedirect.put(hookName, hookStdOut); return this; } + + /** + * Sets the signing key + * <p> + * Per spec of user.signingKey: this will be sent to the GPG program as is, + * i.e. can be anything supported by the GPG program. + * </p> + * <p> + * Note, if none was set or <code>null</code> is specified a default will be + * obtained from the configuration. + * </p> + * + * @param signingKey + * signing key (maybe <code>null</code>) + * @return {@code this} + * @since 5.3 + */ + public CommitCommand setSigningKey(String signingKey) { + checkCallable(); + this.signingKey = signingKey; + return this; + } + + /** + * Sets whether the commit should be signed. + * + * @param sign + * <code>true</code> to sign, <code>false</code> to not sign and + * <code>null</code> for default behavior (read from + * configuration) + * @return {@code this} + * @since 5.3 + */ + public CommitCommand setSign(Boolean sign) { + checkCallable(); + this.signCommit = sign; + return this; + } + + /** + * Sets a {@link CredentialsProvider} + * + * @param credentialsProvider + * the provider to use when querying for credentials (eg., during + * signing) + * @since 5.3 + */ + public void setCredentialsProvider( + CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java index 73e93a1c94..2c9c5f20cc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java @@ -193,7 +193,8 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { // updated to an object that is not currently present in the // submodule. if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND - && !submoduleRepo.hasObject(walk.getObjectId())) + && !submoduleRepo.getObjectDatabase() + .has(walk.getObjectId())) || recurseMode == FetchRecurseSubmodulesMode.YES) { FetchCommand f = new FetchCommand(submoduleRepo) .setProgressMonitor(monitor) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java index 9653c365b2..0e3d000d3a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -158,11 +158,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private static final String ONTO = "onto"; //$NON-NLS-1$ - private static final String ONTO_NAME = "onto-name"; //$NON-NLS-1$ + private static final String ONTO_NAME = "onto_name"; //$NON-NLS-1$ private static final String PATCH = "patch"; //$NON-NLS-1$ - private static final String REBASE_HEAD = "head"; //$NON-NLS-1$ + private static final String REBASE_HEAD = "orig-head"; //$NON-NLS-1$ + + /** Pre git 1.7.6 file name for {@link #REBASE_HEAD}. */ + private static final String REBASE_HEAD_LEGACY = "head"; //$NON-NLS-1$ private static final String AMEND = "amend"; //$NON-NLS-1$ @@ -177,6 +180,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> { /** * The folder containing the hashes of (potentially) rewritten commits when * --preserve-merges is used. + * <p> + * Native git rebase --merge uses a <em>file</em> of that name to record + * commits to copy notes at the end of the whole rebase. + * </p> */ private static final String REWRITTEN = "rewritten"; //$NON-NLS-1$ @@ -289,7 +296,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } this.upstreamCommit = walk.parseCommit(repo .resolve(upstreamCommitId)); - preserveMerges = rebaseState.getRewrittenDir().exists(); + preserveMerges = rebaseState.getRewrittenDir().isDirectory(); break; case BEGIN: autoStash(); @@ -1120,10 +1127,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> { repo.writeOrigHead(headId); rebaseState.createFile(REBASE_HEAD, headId.name()); + rebaseState.createFile(REBASE_HEAD_LEGACY, headId.name()); rebaseState.createFile(HEAD_NAME, headName); rebaseState.createFile(ONTO, upstreamCommit.name()); rebaseState.createFile(ONTO_NAME, upstreamCommitName); - if (isInteractive()) { + if (isInteractive() || preserveMerges) { + // --preserve-merges is an interactive mode for native git. Without + // this, native git rebase --continue after a conflict would fall + // into merge mode. rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$ } rebaseState.createFile(QUIET, ""); //$NON-NLS-1$ @@ -1333,8 +1344,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private RebaseResult abort(RebaseResult result) throws IOException, GitAPIException { + ObjectId origHead = getOriginalHead(); try { - ObjectId origHead = repo.readOrigHead(); String commitId = origHead != null ? origHead.name() : null; monitor.beginTask(MessageFormat.format( JGitText.get().abortingRebase, commitId), @@ -1373,7 +1384,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { // update the HEAD res = refUpdate.link(headName); } else { - refUpdate.setNewObjectId(repo.readOrigHead()); + refUpdate.setNewObjectId(origHead); res = refUpdate.forceUpdate(); } @@ -1400,6 +1411,19 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } } + private ObjectId getOriginalHead() throws IOException { + try { + return ObjectId.fromString(rebaseState.readFile(REBASE_HEAD)); + } catch (FileNotFoundException e) { + try { + return ObjectId + .fromString(rebaseState.readFile(REBASE_HEAD_LEGACY)); + } catch (FileNotFoundException ex) { + return repo.readOrigHead(); + } + } + } + private boolean checkoutCommit(String headName, RevCommit commit) throws IOException, CheckoutConflictException { @@ -1706,7 +1730,20 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } public String readFile(String name) throws IOException { - return readFile(getDir(), name); + try { + return readFile(getDir(), name); + } catch (FileNotFoundException e) { + if (ONTO_NAME.equals(name)) { + // Older JGit mistakenly wrote a file "onto-name" instead of + // "onto_name". Try that wrong name just in case somebody + // upgraded while a rebase started by JGit was in progress. + File oldFile = getFile(ONTO_NAME.replace('_', '-')); + if (oldFile.exists()) { + return readFile(oldFile); + } + } + throw e; + } } public void createFile(String name, String content) throws IOException { @@ -1721,14 +1758,18 @@ public class RebaseCommand extends GitCommand<RebaseResult> { return (getDir().getName() + "/" + name); //$NON-NLS-1$ } - private static String readFile(File directory, String fileName) - throws IOException { - byte[] content = IO.readFully(new File(directory, fileName)); + private static String readFile(File file) throws IOException { + byte[] content = IO.readFully(file); // strip off the last LF int end = RawParseUtils.prevLF(content, content.length); return RawParseUtils.decode(content, 0, end + 1); } + private static String readFile(File directory, String fileName) + throws IOException { + return readFile(new File(directory, fileName)); + } + private static void createFile(File parentDir, String name, String content) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java index 7a5885cfda..016cb15d90 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java @@ -65,7 +65,7 @@ import org.eclipse.jgit.transport.RemoteConfig; */ public class RemoteRemoveCommand extends GitCommand<RemoteConfig> { - private String name; + private String remoteName; /** * <p> @@ -84,9 +84,24 @@ public class RemoteRemoveCommand extends GitCommand<RemoteConfig> { * * @param name * a remote name + * @deprecated use {@link #setRemoteName} instead */ + @Deprecated public void setName(String name) { - this.name = name; + this.remoteName = name; + } + + /** + * The name of the remote to remove. + * + * @param remoteName + * a remote name + * @return {@code this} + * @since 5.3 + */ + public RemoteRemoveCommand setRemoteName(String remoteName) { + this.remoteName = remoteName; + return this; } /** @@ -101,8 +116,8 @@ public class RemoteRemoveCommand extends GitCommand<RemoteConfig> { try { StoredConfig config = repo.getConfig(); - RemoteConfig remote = new RemoteConfig(config, name); - config.unsetSection(ConfigConstants.CONFIG_KEY_REMOTE, name); + RemoteConfig remote = new RemoteConfig(config, remoteName); + config.unsetSection(ConfigConstants.CONFIG_KEY_REMOTE, remoteName); config.save(); return remote; } catch (IOException | URISyntaxException e) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java index d7b7a31bd6..21d4023d67 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java @@ -54,7 +54,7 @@ import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; /** - * Used to to change the URL of a remote. + * Used to change the URL of a remote. * * This class has setters for all supported options and arguments of this * command and a {@link #call()} method to finally execute the command. @@ -66,11 +66,28 @@ import org.eclipse.jgit.transport.URIish; */ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { - private String name; + /** + * The available URI types for the remote. + * + * @since 5.3 + */ + public enum UriType { + /** + * Fetch URL for the remote. + */ + FETCH, + /** + * Push URL for the remote. + */ + PUSH + } - private URIish uri; - private boolean push; + private String remoteName; + + private URIish remoteUri; + + private UriType type; /** * <p> @@ -89,9 +106,24 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { * * @param name * a remote name + * @deprecated use {@link #setRemoteName} instead */ + @Deprecated public void setName(String name) { - this.name = name; + this.remoteName = name; + } + + /** + * The name of the remote to change the URL for. + * + * @param remoteName + * a remote remoteName + * @return {@code this} + * @since 5.3 + */ + public RemoteSetUrlCommand setRemoteName(String remoteName) { + this.remoteName = remoteName; + return this; } /** @@ -99,9 +131,24 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { * * @param uri * an URL for the remote + * @deprecated use {@link #setRemoteUri} instead */ + @Deprecated public void setUri(URIish uri) { - this.uri = uri; + this.remoteUri = uri; + } + + /** + * The new URL for the remote. + * + * @param remoteUri + * an URL for the remote + * @return {@code this} + * @since 5.3 + */ + public RemoteSetUrlCommand setRemoteUri(URIish remoteUri) { + this.remoteUri = remoteUri; + return this; } /** @@ -110,9 +157,28 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { * @param push * <code>true</code> to set the push url, <code>false</code> to * set the fetch url + * @deprecated use {@link #setUriType} instead */ + @Deprecated public void setPush(boolean push) { - this.push = push; + if (push) { + setUriType(UriType.PUSH); + } else { + setUriType(UriType.FETCH); + } + } + + /** + * Whether to change the push URL of the remote instead of the fetch URL. + * + * @param type + * the <code>UriType</code> value to set + * @return {@code this} + * @since 5.3 + */ + public RemoteSetUrlCommand setUriType(UriType type) { + this.type = type; + return this; } /** @@ -127,8 +193,8 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { try { StoredConfig config = repo.getConfig(); - RemoteConfig remote = new RemoteConfig(config, name); - if (push) { + RemoteConfig remote = new RemoteConfig(config, remoteName); + if (type == UriType.PUSH) { List<URIish> uris = remote.getPushURIs(); if (uris.size() > 1) { throw new JGitInternalException( @@ -136,7 +202,7 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { } else if (uris.size() == 1) { remote.removePushURI(uris.get(0)); } - remote.addPushURI(uri); + remote.addPushURI(remoteUri); } else { List<URIish> uris = remote.getURIs(); if (uris.size() > 1) { @@ -145,7 +211,7 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { } else if (uris.size() == 1) { remote.removeURI(uris.get(0)); } - remote.addURI(uri); + remote.addURI(remoteUri); } remote.update(config); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java index 24d9dd4015..7e8c33c8a1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java @@ -94,25 +94,38 @@ public class RenameBranchCommand extends GitCommand<Ref> { RefAlreadyExistsException, DetachedHeadException { checkCallable(); - if (newName == null) + if (newName == null) { throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, "<null>")); //$NON-NLS-1$ - + } try { String fullOldName; String fullNewName; - if (repo.findRef(newName) != null) - throw new RefAlreadyExistsException(MessageFormat.format( - JGitText.get().refAlreadyExists1, newName)); if (oldName != null) { - Ref ref = repo.findRef(oldName); - if (ref == null) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().refNotResolved, oldName)); - if (ref.getName().startsWith(Constants.R_TAGS)) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().renameBranchFailedBecauseTag, - oldName)); + // Don't just rely on findRef -- if there are local and remote + // branches with the same name, and oldName is a short name, it + // does not uniquely identify the ref and we might end up + // renaming the wrong branch or finding a tag instead even + // if a unique branch for the name exists! + // + // OldName may be a either a short or a full name. + Ref ref = repo.exactRef(oldName); + if (ref == null) { + ref = repo.exactRef(Constants.R_HEADS + oldName); + Ref ref2 = repo.exactRef(Constants.R_REMOTES + oldName); + if (ref != null && ref2 != null) { + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().renameBranchFailedAmbiguous, + oldName, ref.getName(), ref2.getName())); + } else if (ref == null) { + if (ref2 != null) { + ref = ref2; + } else { + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().refNotResolved, oldName)); + } + } + } fullOldName = ref.getName(); } else { fullOldName = repo.getFullBranch(); @@ -124,26 +137,34 @@ public class RenameBranchCommand extends GitCommand<Ref> { throw new DetachedHeadException(); } - if (fullOldName.startsWith(Constants.R_REMOTES)) + if (fullOldName.startsWith(Constants.R_REMOTES)) { fullNewName = Constants.R_REMOTES + newName; - else { + } else if (fullOldName.startsWith(Constants.R_HEADS)) { fullNewName = Constants.R_HEADS + newName; + } else { + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().renameBranchFailedNotABranch, + fullOldName)); } - if (!Repository.isValidRefName(fullNewName)) + if (!Repository.isValidRefName(fullNewName)) { throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, fullNewName)); - + } + if (repo.exactRef(fullNewName) != null) { + throw new RefAlreadyExistsException(MessageFormat + .format(JGitText.get().refAlreadyExists1, fullNewName)); + } RefRename rename = repo.renameRef(fullOldName, fullNewName); Result renameResult = rename.rename(); setCallable(false); - if (Result.RENAMED != renameResult) + if (Result.RENAMED != renameResult) { throw new JGitInternalException(MessageFormat.format(JGitText .get().renameBranchUnexpectedResult, renameResult .name())); - + } if (fullNewName.startsWith(Constants.R_HEADS)) { String shortOldName = fullOldName.substring(Constants.R_HEADS .length()); @@ -154,8 +175,9 @@ public class RenameBranchCommand extends GitCommand<Ref> { String[] values = repoConfig.getStringList( ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName, name); - if (values.length == 0) + if (values.length == 0) { continue; + } // Keep any existing values already configured for the // new branch name String[] existing = repoConfig.getStringList( @@ -180,10 +202,11 @@ public class RenameBranchCommand extends GitCommand<Ref> { repoConfig.save(); } - Ref resultRef = repo.findRef(newName); - if (resultRef == null) + Ref resultRef = repo.exactRef(fullNewName); + if (resultRef == null) { throw new JGitInternalException( JGitText.get().renameBranchFailedUnknownReason); + } return resultRef; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); @@ -191,7 +214,13 @@ public class RenameBranchCommand extends GitCommand<Ref> { } /** - * Set the new name of the branch + * Sets the new short name of the branch. + * <p> + * The full name is constructed using the prefix of the branch to be renamed + * defined by either {@link #setOldName(String)} or HEAD. If that old branch + * is a local branch, the renamed branch also will be, and if the old branch + * is a remote branch, so will be the renamed branch. + * </p> * * @param newName * the new name @@ -204,7 +233,11 @@ public class RenameBranchCommand extends GitCommand<Ref> { } /** - * Set the old name of the branch + * Sets the old name of the branch. + * <p> + * {@code oldName} may be a short or a full name. Using a full name is + * recommended to unambiguously identify the branch to be renamed. + * </p> * * @param oldName * the name of the branch to rename; if not set, the currently diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java index ff7c4c64bc..aeb9395c1a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java @@ -96,9 +96,9 @@ public class StashApplyCommand extends GitCommand<ObjectId> { private String stashRef; - private boolean applyIndex = true; + private boolean restoreIndex = true; - private boolean applyUntracked = true; + private boolean restoreUntracked = true; private boolean ignoreRepositoryState; @@ -196,7 +196,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { .getParent(1)); ObjectId stashHeadCommit = stashCommit.getParent(0); ObjectId untrackedCommit = null; - if (applyUntracked && stashCommit.getParentCount() == 3) + if (restoreUntracked && stashCommit.getParentCount() == 3) untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2)); ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo); @@ -216,7 +216,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { dc, merger.getResultTreeId()); dco.setFailOnConflict(true); dco.checkout(); // Ignoring failed deletes.... - if (applyIndex) { + if (restoreIndex) { ResolveMerger ixMerger = (ResolveMerger) strategy .newMerger(repo, true); ixMerger.setCommitNames(new String[] { "stashed HEAD", //$NON-NLS-1$ @@ -277,9 +277,24 @@ public class StashApplyCommand extends GitCommand<ObjectId> { * * @param applyIndex * true (default) if the command should restore the index state + * @deprecated use {@link #setRestoreIndex} instead */ + @Deprecated public void setApplyIndex(boolean applyIndex) { - this.applyIndex = applyIndex; + this.restoreIndex = applyIndex; + } + + /** + * Whether to restore the index state + * + * @param restoreIndex + * true (default) if the command should restore the index state + * @return {@code this} + * @since 5.3 + */ + public StashApplyCommand setRestoreIndex(boolean restoreIndex) { + this.restoreIndex = restoreIndex; + return this; } /** @@ -302,9 +317,24 @@ public class StashApplyCommand extends GitCommand<ObjectId> { * @param applyUntracked * true (default) if the command should restore untracked files * @since 3.4 + * @deprecated use {@link #setRestoreUntracked} instead */ + @Deprecated public void setApplyUntracked(boolean applyUntracked) { - this.applyUntracked = applyUntracked; + this.restoreUntracked = applyUntracked; + } + + /** + * Whether the command should restore untracked files + * + * @param restoreUntracked + * true (default) if the command should restore untracked files + * @return {@code this} + * @since 5.3 + */ + public StashApplyCommand setRestoreUntracked(boolean restoreUntracked) { + this.restoreUntracked = restoreUntracked; + return this; } private void resetIndex(RevTree tree) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java index 57d8a13d10..c723da3e49 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/JGitInternalException.java @@ -44,7 +44,7 @@ package org.eclipse.jgit.api.errors; * <p> * During command execution a lot of exceptions may be thrown. Some of them * represent error situations which can be handled specifically by the caller of - * the command. But a lot of exceptions are so low-level that is is unlikely + * the command. But a lot of exceptions are so low-level that it is unlikely * that the caller of the command can handle them effectively. The huge number * of these low-level exceptions which are thrown by the commands lead to a * complicated and wide interface of the commands. Callers of the API have to diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnsupportedSigningFormatException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnsupportedSigningFormatException.java new file mode 100644 index 0000000000..eb5db6ad16 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnsupportedSigningFormatException.java @@ -0,0 +1,69 @@ +/* + * 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.api.errors; + +/** + * Exception thrown when the configured gpg.format is not supported. + * + * @since 5.3 + */ +public class UnsupportedSigningFormatException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * Constructor for UnsupportedGpgFormatException + * + * @param message + * error message + * @param cause + * a {@link java.lang.Throwable} + */ + public UnsupportedSigningFormatException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructor for UnsupportedGpgFormatException + * + * @param message + * error message + */ + public UnsupportedSigningFormatException(String message) { + super(message); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java index bd41d90680..6c0d90ebad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java @@ -309,6 +309,74 @@ public class RawText extends Sequence { } /** + * Determine heuristically whether a byte array represents text content + * using CR-LF as line separator. + * + * @param raw + * the raw file content. + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @since 5.3 + */ + public static boolean isCrLfText(byte[] raw) { + return isCrLfText(raw, raw.length); + } + + /** + * Determine heuristically whether the bytes contained in a stream represent + * text content using CR-LF as line separator. + * + * Note: Do not further use this stream after having called this method! The + * stream may not be fully read and will be left at an unknown position + * after consuming an unknown number of bytes. The caller is responsible for + * closing the stream. + * + * @param raw + * input stream containing the raw file content. + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @throws java.io.IOException + * if input stream could not be read + * @since 5.3 + */ + public static boolean isCrLfText(InputStream raw) throws IOException { + byte[] buffer = new byte[FIRST_FEW_BYTES]; + int cnt = 0; + while (cnt < buffer.length) { + int n = raw.read(buffer, cnt, buffer.length - cnt); + if (n == -1) { + break; + } + cnt += n; + } + return isCrLfText(buffer, cnt); + } + + /** + * Determine heuristically whether a byte array represents text content + * using CR-LF as line separator. + * + * @param raw + * the raw file content. + * @param length + * number of bytes in {@code raw} to evaluate. + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @since 5.3 + */ + public static boolean isCrLfText(byte[] raw, int length) { + boolean has_crlf = false; + for (int ptr = 0; ptr < length - 1; ptr++) { + if (raw[ptr] == '\0') { + return false; // binary + } else if (raw[ptr] == '\r' && raw[ptr + 1] == '\n') { + has_crlf = true; + } + } + return has_crlf; + } + + /** * Get the line delimiter for the first line. * * @since 2.0 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java index ccd0055585..3bc95a2f2f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SequenceComparator.java @@ -84,7 +84,7 @@ public abstract class SequenceComparator<S extends Sequence> { * method must produce the same integer result for both items. * * It is not required for two items to have different hash values if they - * are are unequal according to the {@code equals()} method. + * are unequal according to the {@code equals()} method. * * @param seq * the sequence. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java index 5897ffb758..539f2370e3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java @@ -236,7 +236,7 @@ public class SimilarityIndex { * A region of a file is defined as a line in a text file or a fixed-size * block in a binary file. To prepare an index, each region in the file is * hashed; the values and counts of hashes are retained in a sorted table. - * Define the similarity fraction F as the the count of matching regions + * Define the similarity fraction F as the count of matching regions * between the two files divided between the maximum count of regions in * either file. The similarity score is F multiplied by the maxScore * constant, yielding a range [0, maxScore]. It is defined as maxScore for 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 8aa97df777..2d6228657a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -156,9 +156,11 @@ public class DirCacheCheckout { private boolean failOnConflict = true; + private boolean force = false; + private ArrayList<String> toBeDeleted = new ArrayList<>(); - private boolean emptyDirCache; + private boolean initialCheckout; private boolean performingCheckout; @@ -231,7 +233,7 @@ public class DirCacheCheckout { this.headCommitTree = headCommitTree; this.mergeCommitTree = mergeCommitTree; this.workingTree = workingTree; - this.emptyDirCache = (dc == null) || (dc.getEntryCount() == 0); + this.initialCheckout = !repo.isBare() && !repo.getIndexFile().exists(); } /** @@ -430,11 +432,11 @@ public class DirCacheCheckout { if (mtime == null || mtime.equals(Instant.EPOCH)) { entry.setLastModified(f.getEntryLastModifiedInstant()); } - keep(entry); + keep(entry, f); } } else // The index contains a folder - keep(i.getDirCacheEntry()); + keep(i.getDirCacheEntry(), f); } else { // There is no entry in the merge commit. Means: we want to delete // what's currently in the index and working tree @@ -824,14 +826,14 @@ public class DirCacheCheckout { break; case 0xDFD: // 3 4 - keep(dce); + keep(dce, f); break; case 0xF0D: // 18 remove(name); break; case 0xDFF: // 5 5b 6 6b if (equalIdAndMode(iId, iMode, mId, mMode)) - keep(dce); // 5 6 + keep(dce, f); // 5 6 else conflict(name, dce, h, m); // 5b 6b break; @@ -861,7 +863,7 @@ public class DirCacheCheckout { conflict(name, dce, h, m); // 9 break; case 0xFD0: // keep without a rule - keep(dce); + keep(dce, f); break; case 0xFFD: // 12 13 14 if (equalIdAndMode(hId, hMode, iId, iMode)) @@ -881,7 +883,7 @@ public class DirCacheCheckout { conflict(name, dce, h, m); break; default: - keep(dce); + keep(dce, f); } return; } @@ -964,10 +966,10 @@ public class DirCacheCheckout { // called before). Ignore the cached deletion and use what we // find in Merge. Potentially updates the file. if (equalIdAndMode(hId, hMode, mId, mMode)) { - if (emptyDirCache) + if (initialCheckout) update(name, mId, mMode); else - keep(dce); + keep(dce, f); } else conflict(name, dce, h, m); } @@ -1030,7 +1032,7 @@ public class DirCacheCheckout { // Nothing in Head // Something in Index // -> Merge contains nothing new. Keep the index. - keep(dce); + keep(dce, f); } else // Merge contains something and it is not the same as Index // Nothing in Head @@ -1179,7 +1181,7 @@ public class DirCacheCheckout { // to the other one. // -> In all three cases we don't touch index and file. - keep(dce); + keep(dce, f); } } } @@ -1228,9 +1230,15 @@ public class DirCacheCheckout { } } - private void keep(DirCacheEntry e) { + private void keep(DirCacheEntry e, WorkingTreeIterator f) + throws IOException { if (e != null && !FileMode.TREE.equals(e.getFileMode())) builder.add(e); + if (force) { + if (f.isModified(e, true, this.walk.getObjectReader())) { + checkoutEntry(repo, e, this.walk.getObjectReader()); + } + } } private void remove(String path) { @@ -1265,6 +1273,20 @@ public class DirCacheCheckout { } /** + * If <code>true</code>, dirty worktree files may be overridden. If + * <code>false</code> dirty worktree files will not be overridden in order + * not to delete unsaved content. This corresponds to native git's 'git + * checkout -f' option. By default this option is set to false. + * + * @param force + * a boolean. + * @since 5.3 + */ + public void setForce(boolean force) { + this.force = force; + } + + /** * This method implements how to handle conflicts when * {@link #failOnConflict} is false * 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 e9d86dfa83..cb62925a1f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -147,7 +147,7 @@ public class RepoCommand extends GitCommand<RevCommit> { * 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 + * "master" which 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. @@ -187,7 +187,7 @@ public class RepoCommand extends GitCommand<RevCommit> { * 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 + * "master" which 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 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 5bbfe81dff..039a6b2b43 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -286,6 +286,7 @@ public class JGitText extends TranslationBundle { /***/ public String createNewFileFailed; /***/ public String createRequiresZeroOldId; /***/ public String credentialPassword; + /***/ public String credentialPassphrase; /***/ public String credentialUsername; /***/ public String daemonAlreadyRunning; /***/ public String daysAgo; @@ -387,6 +388,14 @@ public class JGitText extends TranslationBundle { /***/ public String gcLogExists; /***/ public String gcTooManyUnpruned; /***/ public String gitmodulesNotFound; + /***/ public String gpgFailedToParseSecretKey; + /***/ public String gpgNoCredentialsProvider; + /***/ public String gpgNoKeyring; + /***/ public String gpgNoKeyInLegacySecring; + /***/ public String gpgNoPublicKeyFound; + /***/ public String gpgNoSecretKeyForPublicKey; + /***/ public String gpgKeyInfo; + /***/ public String gpgSigningCancelled; /***/ public String headRequiredToStash; /***/ public String hoursAgo; /***/ public String httpConfigCannotNormalizeURL; @@ -571,6 +580,7 @@ public class JGitText extends TranslationBundle { /***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable; /***/ public String onlyOneFetchSupported; /***/ public String onlyOneOperationCallPerConnectionIsSupported; + /***/ public String onlyOpenPgpSupportedForSigning; /***/ public String openFilesMustBeAtLeast1; /***/ public String openingConnection; /***/ public String operationCanceled; @@ -649,8 +659,9 @@ public class JGitText extends TranslationBundle { /***/ public String remoteDoesNotHaveSpec; /***/ public String remoteDoesNotSupportSmartHTTPPush; /***/ public String remoteHungUpUnexpectedly; - /***/ public String remoteNameCantBeNull; - /***/ public String renameBranchFailedBecauseTag; + /***/ public String remoteNameCannotBeNull; + /***/ public String renameBranchFailedAmbiguous; + /***/ public String renameBranchFailedNotABranch; /***/ public String renameBranchFailedUnknownReason; /***/ public String renameBranchUnexpectedResult; /***/ public String renameCancelled; @@ -690,7 +701,7 @@ public class JGitText extends TranslationBundle { /***/ public String sequenceTooLargeForDiffAlgorithm; /***/ public String serviceNotEnabledNoName; /***/ public String serviceNotPermitted; - /***/ public String sha1CollisionDetected1; + /***/ public String sha1CollisionDetected; /***/ public String shallowCommitsAlreadyInitialized; /***/ public String shallowPacksRequireDepthWalk; /***/ public String shortCompressedStreamAt; @@ -786,6 +797,7 @@ public class JGitText extends TranslationBundle { /***/ public String unableToRemovePath; /***/ public String unableToStore; /***/ public String unableToWrite; + /***/ public String unableToSignCommitNoSecretKey; /***/ public String unauthorized; /***/ public String underflowedReftableBlock; /***/ public String unencodeableFile; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java index 58d12ab233..c7d6c584e4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java @@ -48,7 +48,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.text.MessageFormat; -import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.PackInvalidException; import org.eclipse.jgit.internal.storage.pack.PackExt; @@ -133,19 +132,19 @@ abstract class BlockBasedFile { } DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException { - return cache.getOrLoad(this, pos, ctx, null); + try (LazyChannel c = new LazyChannel(ctx, desc, ext)) { + return cache.getOrLoad(this, pos, ctx, c); + } } - DfsBlock readOneBlock(long pos, DfsReader ctx, - @Nullable ReadableChannel fileChannel) throws IOException { + DfsBlock readOneBlock(long pos, DfsReader ctx, ReadableChannel rc) + throws IOException { if (invalid) { throw new PackInvalidException(getFileName(), invalidatingCause); } ctx.stats.readBlock++; long start = System.nanoTime(); - ReadableChannel rc = fileChannel != null ? fileChannel - : ctx.db.openFile(desc, ext); try { int size = blockSize(rc); pos = (pos / size) * size; @@ -193,9 +192,6 @@ abstract class BlockBasedFile { return new DfsBlock(key, pos, buf); } finally { - if (rc != fileChannel) { - rc.close(); - } ctx.stats.readBlockMicros += elapsedMicros(start); } } @@ -211,4 +207,37 @@ abstract class BlockBasedFile { static long elapsedMicros(long start) { return (System.nanoTime() - start) / 1000L; } + + /** + * A supplier of readable channel that opens the channel lazily. + */ + private static class LazyChannel + implements AutoCloseable, DfsBlockCache.ReadableChannelSupplier { + private final DfsReader ctx; + private final DfsPackDescription desc; + private final PackExt ext; + + private ReadableChannel rc; + + LazyChannel(DfsReader ctx, DfsPackDescription desc, PackExt ext) { + this.ctx = ctx; + this.desc = desc; + this.ext = ext; + } + + @Override + public ReadableChannel get() throws IOException { + if (rc == null) { + rc = ctx.db.openFile(desc, ext); + } + return rc; + } + + @Override + public void close() throws IOException { + if (rc != null) { + rc.close(); + } + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java index 46879529b5..c6e2fae42f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java @@ -49,9 +49,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; import java.util.stream.LongStream; -import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackExt; @@ -128,9 +128,18 @@ public final class DfsBlockCache { /** Hash bucket directory; entries are chained below. */ private final AtomicReferenceArray<HashEntry> table; - /** Locks to prevent concurrent loads for same (PackFile,position). */ + /** + * Locks to prevent concurrent loads for same (PackFile,position) block. The + * number of locks is {@link DfsBlockCacheConfig#getConcurrencyLevel()} to + * cap the overall concurrent block loads. + */ private final ReentrantLock[] loadLocks; + /** + * A separate pool of locks to prevent concurrent loads for same index or bitmap from PackFile. + */ + private final ReentrantLock[] refLocks; + /** Maximum number of bytes the cache should hold. */ private final long maxBytes; @@ -177,19 +186,30 @@ public final class DfsBlockCache { /** Protects the clock and its related data. */ private final ReentrantLock clockLock; + /** + * A consumer of object reference lock wait time milliseconds. May be used to build a metric. + */ + private final Consumer<Long> refLockWaitTime; + /** Current position of the clock. */ private Ref clockHand; @SuppressWarnings("unchecked") private DfsBlockCache(DfsBlockCacheConfig cfg) { tableSize = tableSize(cfg); - if (tableSize < 1) + if (tableSize < 1) { throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1); + } table = new AtomicReferenceArray<>(tableSize); loadLocks = new ReentrantLock[cfg.getConcurrencyLevel()]; - for (int i = 0; i < loadLocks.length; i++) + for (int i = 0; i < loadLocks.length; i++) { loadLocks[i] = new ReentrantLock(true /* fair */); + } + refLocks = new ReentrantLock[cfg.getConcurrencyLevel()]; + for (int i = 0; i < refLocks.length; i++) { + refLocks[i] = new ReentrantLock(true /* fair */); + } maxBytes = cfg.getBlockLimit(); maxStreamThroughCache = (long) (maxBytes * cfg.getStreamRatio()); @@ -207,6 +227,8 @@ public final class DfsBlockCache { statMiss = new AtomicReference<>(newCounters()); statEvict = new AtomicReference<>(newCounters()); liveBytes = new AtomicReference<>(newCounters()); + + refLockWaitTime = cfg.getRefLockWaitTimeConsumer(); } boolean shouldCopyThroughCache(long length) { @@ -333,15 +355,17 @@ public final class DfsBlockCache { private static int tableSize(DfsBlockCacheConfig cfg) { final int wsz = cfg.getBlockSize(); final long limit = cfg.getBlockLimit(); - if (wsz <= 0) + if (wsz <= 0) { throw new IllegalArgumentException(JGitText.get().invalidWindowSize); - if (limit < wsz) + } + if (limit < wsz) { throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); + } return (int) Math.min(5 * (limit / wsz) / 2, Integer.MAX_VALUE); } /** - * Lookup a cached object, creating and loading it if it doesn't exist. + * Look up a cached object, creating and loading it if it doesn't exist. * * @param file * the pack that "contains" the cached object. @@ -350,13 +374,13 @@ public final class DfsBlockCache { * @param ctx * current thread's reader. * @param fileChannel - * optional channel to read {@code pack}. + * supplier for channel to read {@code pack}. * @return the object reference. * @throws IOException * the reference was not in the cache and could not be loaded. */ DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader ctx, - @Nullable ReadableChannel fileChannel) throws IOException { + ReadableChannelSupplier fileChannel) throws IOException { final long requestedPosition = position; position = file.alignToBlock(position); @@ -388,11 +412,13 @@ public final class DfsBlockCache { getStat(statMiss, key).incrementAndGet(); boolean credit = true; try { - v = file.readOneBlock(requestedPosition, ctx, fileChannel); + v = file.readOneBlock(requestedPosition, ctx, + fileChannel.get()); credit = false; } finally { - if (credit) + if (credit) { creditSpace(blockSize, key); + } } if (position != v.start) { // The file discovered its blockSize and adjusted. @@ -405,8 +431,9 @@ public final class DfsBlockCache { ref.hot = true; for (;;) { HashEntry n = new HashEntry(clean(e2), ref); - if (table.compareAndSet(slot, e2, n)) + if (table.compareAndSet(slot, e2, n)) { break; + } e2 = table.get(slot); } addToClock(ref, blockSize - v.size()); @@ -416,8 +443,9 @@ public final class DfsBlockCache { // If the block size changed from the default, it is possible the block // that was loaded is the wrong block for the requested position. - if (v.contains(file.key, requestedPosition)) + if (v.contains(file.key, requestedPosition)) { return v; + } return getOrLoad(file, requestedPosition, ctx, fileChannel); } @@ -488,6 +516,63 @@ public final class DfsBlockCache { put(v.stream, v.start, v.size(), v); } + /** + * Look up a cached object, creating and loading it if it doesn't exist. + * + * @param key + * the stream key of the pack. + * @param loader + * the function to load the reference. + * @return the object reference. + * @throws IOException + * the reference was not in the cache and could not be loaded. + */ + <T> Ref<T> getOrLoadRef(DfsStreamKey key, RefLoader<T> loader) + throws IOException { + int slot = slot(key, 0); + HashEntry e1 = table.get(slot); + Ref<T> ref = scanRef(e1, key, 0); + if (ref != null) { + getStat(statHit, key).incrementAndGet(); + return ref; + } + + ReentrantLock regionLock = lockForRef(key); + long lockStart = System.currentTimeMillis(); + regionLock.lock(); + try { + HashEntry e2 = table.get(slot); + if (e2 != e1) { + ref = scanRef(e2, key, 0); + if (ref != null) { + getStat(statHit, key).incrementAndGet(); + return ref; + } + } + + if (refLockWaitTime != null) { + refLockWaitTime.accept( + Long.valueOf(System.currentTimeMillis() - lockStart)); + } + getStat(statMiss, key).incrementAndGet(); + ref = loader.load(); + ref.hot = true; + // Reserve after loading to get the size of the object + reserveSpace(ref.size, key); + for (;;) { + HashEntry n = new HashEntry(clean(e2), ref); + if (table.compareAndSet(slot, e2, n)) { + break; + } + e2 = table.get(slot); + } + addToClock(ref, 0); + } finally { + regionLock.unlock(); + } + return ref; + } + <T> Ref<T> putRef(DfsStreamKey key, long size, T v) { return put(key, 0, (int) Math.min(size, Integer.MAX_VALUE), v); } @@ -496,8 +581,9 @@ public final class DfsBlockCache { int slot = slot(key, pos); HashEntry e1 = table.get(slot); Ref<T> ref = scanRef(e1, key, pos); - if (ref != null) + if (ref != null) { return ref; + } reserveSpace(size, key); ReentrantLock regionLock = lockFor(key, pos); @@ -516,8 +602,9 @@ public final class DfsBlockCache { ref.hot = true; for (;;) { HashEntry n = new HashEntry(clean(e2), ref); - if (table.compareAndSet(slot, e2, n)) + if (table.compareAndSet(slot, e2, n)) { break; + } e2 = table.get(slot); } addToClock(ref, 0); @@ -534,10 +621,11 @@ public final class DfsBlockCache { @SuppressWarnings("unchecked") <T> T get(DfsStreamKey key, long position) { T val = (T) scan(table.get(slot(key, position)), key, position); - if (val == null) + if (val == null) { getStat(statMiss, key).incrementAndGet(); - else + } else { getStat(statHit, key).incrementAndGet(); + } return val; } @@ -546,21 +634,13 @@ public final class DfsBlockCache { return r != null ? r.get() : null; } - <T> Ref<T> getRef(DfsStreamKey key) { - Ref<T> r = scanRef(table.get(slot(key, 0)), key, 0); - if (r != null) - getStat(statHit, key).incrementAndGet(); - else - getStat(statMiss, key).incrementAndGet(); - return r; - } - @SuppressWarnings("unchecked") private <T> Ref<T> scanRef(HashEntry n, DfsStreamKey key, long position) { for (; n != null; n = n.next) { Ref<T> r = n.ref; - if (r.position == position && r.key.equals(key)) + if (r.position == position && r.key.equals(key)) { return r.get() != null ? r : null; + } } return null; } @@ -573,6 +653,10 @@ public final class DfsBlockCache { return loadLocks[(hash(key.hash, position) >>> 1) % loadLocks.length]; } + private ReentrantLock lockForRef(DfsStreamKey key) { + return refLocks[(key.hash >>> 1) % refLocks.length]; + } + private static AtomicLong[] newCounters() { AtomicLong[] ret = new AtomicLong[PackExt.values().length]; for (int i = 0; i < ret.length; i++) { @@ -613,8 +697,9 @@ public final class DfsBlockCache { private static HashEntry clean(HashEntry top) { while (top != null && top.ref.next == null) top = top.next; - if (top == null) + if (top == null) { return null; + } HashEntry n = clean(top.next); return n == top.next ? top : new HashEntry(n, top.ref); } @@ -649,8 +734,9 @@ public final class DfsBlockCache { T get() { T v = value; - if (v != null) + if (v != null) { hot = true; + } return v; } @@ -658,4 +744,21 @@ public final class DfsBlockCache { return value != null; } } + + @FunctionalInterface + interface RefLoader<T> { + Ref<T> load() throws IOException; + } + + /** + * Supplier for readable channel + */ + @FunctionalInterface + interface ReadableChannelSupplier { + /** + * @return ReadableChannel + * @throws IOException + */ + ReadableChannel get() throws IOException; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java index dd7cb89c96..cd1fa5f78f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java @@ -51,6 +51,7 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONCURRENCY_LEVEL; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO; import java.text.MessageFormat; +import java.util.function.Consumer; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; @@ -71,6 +72,8 @@ public class DfsBlockCacheConfig { private double streamRatio; private int concurrencyLevel; + private Consumer<Long> refLock; + /** * Create a default configuration. */ @@ -194,6 +197,27 @@ public class DfsBlockCacheConfig { } /** + * Get the consumer of the object reference lock wait time in milliseconds. + * + * @return consumer of wait time in milliseconds. + */ + public Consumer<Long> getRefLockWaitTimeConsumer() { + return refLock; + } + + /** + * Set the consumer for lock wait time. + * + * @param c + * consumer of wait time in milliseconds. + * @return {@code this} + */ + public DfsBlockCacheConfig setRefLockWaitTimeConsumer(Consumer<Long> c) { + refLock = c; + return this; + } + + /** * Update properties by setting fields from the configuration. * <p> * If a property is not defined in the configuration, then it is left diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java index c131f9457e..be1387ed0c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java @@ -88,6 +88,8 @@ import org.eclipse.jgit.util.LongList; * objects are similar. */ public final class DfsPackFile extends BlockBasedFile { + private static final int REC_SIZE = Constants.OBJECT_ID_LENGTH + 8; + /** * Lock for initialization of {@link #index} and {@link #corruptObjects}. * <p> @@ -96,13 +98,13 @@ public final class DfsPackFile extends BlockBasedFile { private final Object initLock = new Object(); /** Index mapping {@link ObjectId} to position within the pack stream. */ - private volatile DfsBlockCache.Ref<PackIndex> index; + private volatile PackIndex index; /** Reverse version of {@link #index} mapping position to {@link ObjectId}. */ - private volatile DfsBlockCache.Ref<PackReverseIndex> reverseIndex; + private volatile PackReverseIndex reverseIndex; /** Index of compressed bitmap mapping entire object graph. */ - private volatile DfsBlockCache.Ref<PackBitmapIndex> bitmapIndex; + private volatile PackBitmapIndex bitmapIndex; /** * Objects we have tried to read, and discovered to be corrupt. @@ -148,15 +150,15 @@ public final class DfsPackFile extends BlockBasedFile { * @return whether the pack index file is loaded and cached in memory. */ public boolean isIndexLoaded() { - DfsBlockCache.Ref<PackIndex> idxref = index; - return idxref != null && idxref.has(); + return index != null; } void setPackIndex(PackIndex idx) { long objCnt = idx.getObjectCount(); int recSize = Constants.OBJECT_ID_LENGTH + 8; long sz = objCnt * recSize; - index = cache.putRef(desc.getStreamKey(INDEX), sz, idx); + cache.putRef(desc.getStreamKey(INDEX), sz, idx); + index = idx; } /** @@ -174,11 +176,8 @@ public final class DfsPackFile extends BlockBasedFile { } private PackIndex idx(DfsReader ctx) throws IOException { - DfsBlockCache.Ref<PackIndex> idxref = index; - if (idxref != null) { - PackIndex idx = idxref.get(); - if (idx != null) - return idx; + if (index != null) { + return index; } if (invalid) { @@ -189,56 +188,61 @@ public final class DfsPackFile extends BlockBasedFile { .dispatch(new BeforeDfsPackIndexLoadedEvent(this)); synchronized (initLock) { - idxref = index; - if (idxref != null) { - PackIndex idx = idxref.get(); - if (idx != null) - return idx; - } - - DfsStreamKey idxKey = desc.getStreamKey(INDEX); - idxref = cache.getRef(idxKey); - if (idxref != null) { - PackIndex idx = idxref.get(); - if (idx != null) { - index = idxref; - return idx; - } + if (index != null) { + return index; } - PackIndex idx; try { - ctx.stats.readIdx++; - long start = System.nanoTime(); - try (ReadableChannel rc = ctx.db.openFile(desc, INDEX)) { - InputStream in = Channels.newInputStream(rc); - int wantSize = 8192; - int bs = rc.blockSize(); - if (0 < bs && bs < wantSize) - bs = (wantSize / bs) * bs; - else if (bs <= 0) - bs = wantSize; - idx = PackIndex.read(new BufferedInputStream(in, bs)); - ctx.stats.readIdxBytes += rc.position(); - } finally { - ctx.stats.readIdxMicros += elapsedMicros(start); + DfsStreamKey idxKey = desc.getStreamKey(INDEX); + DfsBlockCache.Ref<PackIndex> idxref = cache.getOrLoadRef(idxKey, + () -> { + try { + ctx.stats.readIdx++; + long start = System.nanoTime(); + try (ReadableChannel rc = ctx.db.openFile(desc, + INDEX)) { + InputStream in = Channels + .newInputStream(rc); + int wantSize = 8192; + int bs = rc.blockSize(); + if (0 < bs && bs < wantSize) { + bs = (wantSize / bs) * bs; + } else if (bs <= 0) { + bs = wantSize; + } + PackIndex idx = PackIndex.read( + new BufferedInputStream(in, bs)); + int sz = (int) Math.min( + idx.getObjectCount() * REC_SIZE, + Integer.MAX_VALUE); + ctx.stats.readIdxBytes += rc.position(); + index = idx; + return new DfsBlockCache.Ref<>(idxKey, 0, + sz, idx); + } finally { + ctx.stats.readIdxMicros += elapsedMicros( + start); + } + } catch (EOFException e) { + throw new IOException(MessageFormat.format( + DfsText.get().shortReadOfIndex, + desc.getFileName(INDEX)), e); + } catch (IOException e) { + throw new IOException(MessageFormat.format( + DfsText.get().cannotReadIndex, + desc.getFileName(INDEX)), e); + } + }); + PackIndex idx = idxref.get(); + if (index == null && idx != null) { + index = idx; } - } catch (EOFException e) { - invalid = true; - invalidatingCause = e; - throw new IOException(MessageFormat.format( - DfsText.get().shortReadOfIndex, - desc.getFileName(INDEX)), e); + return index; } catch (IOException e) { invalid = true; invalidatingCause = e; - throw new IOException(MessageFormat.format( - DfsText.get().cannotReadIndex, - desc.getFileName(INDEX)), e); + throw e; } - - setPackIndex(idx); - return idx; } } @@ -247,102 +251,94 @@ public final class DfsPackFile extends BlockBasedFile { } PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException { - if (invalid || isGarbage() || !desc.hasFileExt(BITMAP_INDEX)) + if (invalid || isGarbage() || !desc.hasFileExt(BITMAP_INDEX)) { return null; + } - DfsBlockCache.Ref<PackBitmapIndex> idxref = bitmapIndex; - if (idxref != null) { - PackBitmapIndex idx = idxref.get(); - if (idx != null) - return idx; + if (bitmapIndex != null) { + return bitmapIndex; } synchronized (initLock) { - idxref = bitmapIndex; - if (idxref != null) { - PackBitmapIndex idx = idxref.get(); - if (idx != null) - return idx; + if (bitmapIndex != null) { + return bitmapIndex; } + PackIndex idx = idx(ctx); + PackReverseIndex revidx = getReverseIdx(ctx); DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX); - idxref = cache.getRef(bitmapKey); - if (idxref != null) { - PackBitmapIndex idx = idxref.get(); - if (idx != null) { - bitmapIndex = idxref; - return idx; - } - } - - long size; - PackBitmapIndex idx; - ctx.stats.readBitmap++; - long start = System.nanoTime(); - try (ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX)) { - try { - InputStream in = Channels.newInputStream(rc); - int wantSize = 8192; - int bs = rc.blockSize(); - if (0 < bs && bs < wantSize) - bs = (wantSize / bs) * bs; - else if (bs <= 0) - bs = wantSize; - in = new BufferedInputStream(in, bs); - idx = PackBitmapIndex.read( - in, idx(ctx), getReverseIdx(ctx)); - } finally { - size = rc.position(); - ctx.stats.readIdxBytes += size; - ctx.stats.readIdxMicros += elapsedMicros(start); - } - } catch (EOFException e) { - throw new IOException(MessageFormat.format( - DfsText.get().shortReadOfIndex, - desc.getFileName(BITMAP_INDEX)), e); - } catch (IOException e) { - throw new IOException(MessageFormat.format( - DfsText.get().cannotReadIndex, - desc.getFileName(BITMAP_INDEX)), e); + DfsBlockCache.Ref<PackBitmapIndex> idxref = cache + .getOrLoadRef(bitmapKey, () -> { + ctx.stats.readBitmap++; + long start = System.nanoTime(); + try (ReadableChannel rc = ctx.db.openFile(desc, + BITMAP_INDEX)) { + long size; + PackBitmapIndex bmidx; + try { + InputStream in = Channels.newInputStream(rc); + int wantSize = 8192; + int bs = rc.blockSize(); + if (0 < bs && bs < wantSize) { + bs = (wantSize / bs) * bs; + } else if (bs <= 0) { + bs = wantSize; + } + in = new BufferedInputStream(in, bs); + bmidx = PackBitmapIndex.read(in, idx, revidx); + } finally { + size = rc.position(); + ctx.stats.readIdxBytes += size; + ctx.stats.readIdxMicros += elapsedMicros(start); + } + int sz = (int) Math.min(size, Integer.MAX_VALUE); + bitmapIndex = bmidx; + return new DfsBlockCache.Ref<>(bitmapKey, 0, sz, + bmidx); + } catch (EOFException e) { + throw new IOException(MessageFormat.format( + DfsText.get().shortReadOfIndex, + desc.getFileName(BITMAP_INDEX)), e); + } catch (IOException e) { + throw new IOException(MessageFormat.format( + DfsText.get().cannotReadIndex, + desc.getFileName(BITMAP_INDEX)), e); + } + }); + PackBitmapIndex bmidx = idxref.get(); + if (bitmapIndex == null && bmidx != null) { + bitmapIndex = bmidx; } - - bitmapIndex = cache.putRef(bitmapKey, size, idx); - return idx; + return bitmapIndex; } } PackReverseIndex getReverseIdx(DfsReader ctx) throws IOException { - DfsBlockCache.Ref<PackReverseIndex> revref = reverseIndex; - if (revref != null) { - PackReverseIndex revidx = revref.get(); - if (revidx != null) - return revidx; + if (reverseIndex != null) { + return reverseIndex; } synchronized (initLock) { - revref = reverseIndex; - if (revref != null) { - PackReverseIndex revidx = revref.get(); - if (revidx != null) - return revidx; - } - - DfsStreamKey revKey = - new DfsStreamKey.ForReverseIndex(desc.getStreamKey(INDEX)); - revref = cache.getRef(revKey); - if (revref != null) { - PackReverseIndex idx = revref.get(); - if (idx != null) { - reverseIndex = revref; - return idx; - } + if (reverseIndex != null) { + return reverseIndex; } PackIndex idx = idx(ctx); - PackReverseIndex revidx = new PackReverseIndex(idx); - long cnt = idx.getObjectCount(); - reverseIndex = cache.putRef(revKey, cnt * 8, revidx); - return revidx; + DfsStreamKey revKey = new DfsStreamKey.ForReverseIndex( + desc.getStreamKey(INDEX)); + DfsBlockCache.Ref<PackReverseIndex> revref = cache + .getOrLoadRef(revKey, () -> { + PackReverseIndex revidx = new PackReverseIndex(idx); + int sz = (int) Math.min(idx.getObjectCount() * 8, + Integer.MAX_VALUE); + reverseIndex = revidx; + return new DfsBlockCache.Ref<>(revKey, 0, sz, revidx); + }); + PackReverseIndex revidx = revref.get(); + if (reverseIndex == null && revidx != null) { + reverseIndex = revidx; + } + return reverseIndex; } } @@ -420,110 +416,94 @@ public final class DfsPackFile extends BlockBasedFile { return null; } - if (ctx.inflate(this, position, dstbuf, false) != sz) + if (ctx.inflate(this, position, dstbuf, false) != sz) { throw new EOFException(MessageFormat.format( JGitText.get().shortCompressedStreamAt, Long.valueOf(position))); + } return dstbuf; } - void copyPackAsIs(PackOutputStream out, DfsReader ctx) - throws IOException { + void copyPackAsIs(PackOutputStream out, DfsReader ctx) throws IOException { // If the length hasn't been determined yet, pin to set it. if (length == -1) { ctx.pin(this, 0); ctx.unpin(); } - if (cache.shouldCopyThroughCache(length)) - copyPackThroughCache(out, ctx); - else - copyPackBypassCache(out, ctx); + try (ReadableChannel rc = ctx.db.openFile(desc, PACK)) { + int sz = ctx.getOptions().getStreamPackBufferSize(); + if (sz > 0) { + rc.setReadAheadBytes(sz); + } + if (cache.shouldCopyThroughCache(length)) { + copyPackThroughCache(out, ctx, rc); + } else { + copyPackBypassCache(out, rc); + } + } } - private void copyPackThroughCache(PackOutputStream out, DfsReader ctx) - throws IOException { - @SuppressWarnings("resource") // Explicitly closed in finally block - ReadableChannel rc = null; - try { - long position = 12; - long remaining = length - (12 + 20); - while (0 < remaining) { - DfsBlock b; - if (rc != null) { - b = cache.getOrLoad(this, position, ctx, rc); - } else { - b = cache.get(key, alignToBlock(position)); - if (b == null) { - rc = ctx.db.openFile(desc, PACK); - int sz = ctx.getOptions().getStreamPackBufferSize(); - if (sz > 0) { - rc.setReadAheadBytes(sz); - } - b = cache.getOrLoad(this, position, ctx, rc); - } - } - - int ptr = (int) (position - b.start); - int n = (int) Math.min(b.size() - ptr, remaining); - b.write(out, position, n); - position += n; - remaining -= n; - } - } finally { - if (rc != null) { - rc.close(); + private void copyPackThroughCache(PackOutputStream out, DfsReader ctx, + ReadableChannel rc) throws IOException { + long position = 12; + long remaining = length - (12 + 20); + while (0 < remaining) { + DfsBlock b = cache.getOrLoad(this, position, ctx, () -> rc); + int ptr = (int) (position - b.start); + if (b.size() <= ptr) { + throw packfileIsTruncated(); } + int n = (int) Math.min(b.size() - ptr, remaining); + b.write(out, position, n); + position += n; + remaining -= n; } } - private long copyPackBypassCache(PackOutputStream out, DfsReader ctx) + private long copyPackBypassCache(PackOutputStream out, ReadableChannel rc) throws IOException { - try (ReadableChannel rc = ctx.db.openFile(desc, PACK)) { - ByteBuffer buf = newCopyBuffer(out, rc); - if (ctx.getOptions().getStreamPackBufferSize() > 0) - rc.setReadAheadBytes(ctx.getOptions().getStreamPackBufferSize()); - long position = 12; - long remaining = length - (12 + 20); - boolean packHeadSkipped = false; - while (0 < remaining) { - DfsBlock b = cache.get(key, alignToBlock(position)); - if (b != null) { - int ptr = (int) (position - b.start); - int n = (int) Math.min(b.size() - ptr, remaining); - b.write(out, position, n); - position += n; - remaining -= n; - rc.position(position); - packHeadSkipped = true; - continue; - } - - buf.position(0); - int n = read(rc, buf); - if (n <= 0) + ByteBuffer buf = newCopyBuffer(out, rc); + long position = 12; + long remaining = length - (12 + 20); + boolean packHeadSkipped = false; + while (0 < remaining) { + DfsBlock b = cache.get(key, alignToBlock(position)); + if (b != null) { + int ptr = (int) (position - b.start); + if (b.size() <= ptr) { throw packfileIsTruncated(); - else if (n > remaining) - n = (int) remaining; - - if (!packHeadSkipped) { - // Need skip the 'PACK' header for the first read - out.write(buf.array(), 12, n - 12); - packHeadSkipped = true; - } else { - out.write(buf.array(), 0, n); } + int n = (int) Math.min(b.size() - ptr, remaining); + b.write(out, position, n); position += n; remaining -= n; + rc.position(position); + packHeadSkipped = true; + continue; } - return position; + + // Need to skip the 'PACK' header for the first read + int ptr = packHeadSkipped ? 0 : 12; + buf.position(0); + int bufLen = read(rc, buf); + if (bufLen <= ptr) { + throw packfileIsTruncated(); + } + int n = (int) Math.min(bufLen - ptr, remaining); + out.write(buf.array(), ptr, n); + position += n; + remaining -= n; + packHeadSkipped = true; } + return position; } private ByteBuffer newCopyBuffer(PackOutputStream out, ReadableChannel rc) { int bs = blockSize(rc); byte[] copyBuf = out.getCopyBuffer(); - if (bs > copyBuf.length) + if (bs > copyBuf.length) { copyBuf = new byte[bs]; + } return ByteBuffer.wrap(copyBuf, 0, bs); } @@ -635,8 +615,9 @@ public final class DfsPackFile extends BlockBasedFile { readFully(pos, buf, 0, n, ctx); crc1.update(buf, 0, n); inf.setInput(buf, 0, n); - while (inf.inflate(tmp, 0, tmp.length) > 0) + while (inf.inflate(tmp, 0, tmp.length) > 0) { continue; + } pos += n; cnt -= n; } @@ -770,8 +751,9 @@ public final class DfsPackFile extends BlockBasedFile { if (sz < ctx.getStreamFileThreshold()) { data = decompress(pos + p, (int) sz, ctx); - if (data != null) + if (data != null) { return new ObjectLoader.SmallObject(typeCode, data); + } } return new LargePackedWholeObject(typeCode, sz, pos, p, this, ctx.db); } @@ -787,8 +769,9 @@ public final class DfsPackFile extends BlockBasedFile { } base = pos - base; delta = new Delta(delta, pos, (int) sz, p, base); - if (sz != delta.deltaSize) + if (sz != delta.deltaSize) { break SEARCH; + } DeltaBaseCache.Entry e = ctx.getDeltaBaseCache().get(key, base); if (e != null) { @@ -805,8 +788,9 @@ public final class DfsPackFile extends BlockBasedFile { readFully(pos + p, ib, 0, 20, ctx); long base = findDeltaBase(ctx, ObjectId.fromRaw(ib)); delta = new Delta(delta, pos, (int) sz, p + 20, base); - if (sz != delta.deltaSize) + if (sz != delta.deltaSize) { break SEARCH; + } DeltaBaseCache.Entry e = ctx.getDeltaBaseCache().get(key, base); if (e != null) { @@ -834,10 +818,11 @@ public final class DfsPackFile extends BlockBasedFile { assert(delta != null); do { // Cache only the base immediately before desired object. - if (cached) + if (cached) { cached = false; - else if (delta.next == null) + } else if (delta.next == null) { ctx.getDeltaBaseCache().put(key, delta.basePos, type, data); + } pos = delta.deltaPos; @@ -848,8 +833,9 @@ public final class DfsPackFile extends BlockBasedFile { } final long sz = BinaryDelta.getResultSize(cmds); - if (Integer.MAX_VALUE <= sz) + if (Integer.MAX_VALUE <= sz) { throw new LargeObjectException.ExceedsByteArrayLimit(); + } final byte[] result; try { @@ -879,9 +865,10 @@ public final class DfsPackFile extends BlockBasedFile { private long findDeltaBase(DfsReader ctx, ObjectId baseId) throws IOException, MissingObjectException { long ofs = idx(ctx).findOffset(baseId); - if (ofs < 0) + if (ofs < 0) { throw new MissingObjectException(baseId, JGitText.get().missingDeltaBase); + } return ofs; } @@ -938,8 +925,9 @@ public final class DfsPackFile extends BlockBasedFile { case Constants.OBJ_OFS_DELTA: { int p = 1; - while ((c & 0x80) != 0) + while ((c & 0x80) != 0) { c = ib[p++] & 0xff; + } c = ib[p++] & 0xff; long ofs = c & 127; while ((c & 128) != 0) { @@ -954,8 +942,9 @@ public final class DfsPackFile extends BlockBasedFile { case Constants.OBJ_REF_DELTA: { int p = 1; - while ((c & 0x80) != 0) + while ((c & 0x80) != 0) { c = ib[p++] & 0xff; + } readFully(pos + p, ib, 0, 20, ctx); pos = findDeltaBase(ctx, ObjectId.fromRaw(ib)); continue; @@ -998,8 +987,9 @@ public final class DfsPackFile extends BlockBasedFile { case Constants.OBJ_OFS_DELTA: c = ib[p++] & 0xff; - while ((c & 128) != 0) + while ((c & 128) != 0) { c = ib[p++] & 0xff; + } deltaAt = pos + p; break; @@ -1032,8 +1022,9 @@ public final class DfsPackFile extends BlockBasedFile { int c = ib[0] & 0xff; int p = 1; final int typeCode = (c >> 4) & 7; - while ((c & 0x80) != 0) + while ((c & 0x80) != 0) { c = ib[p++] & 0xff; + } long len = rev.findNextOffset(pos, length - 20) - pos; switch (typeCode) { @@ -1077,8 +1068,9 @@ public final class DfsPackFile extends BlockBasedFile { boolean isCorrupt(long offset) { LongList list = corruptObjects; - if (list == null) + if (list == null) { return false; + } synchronized (list) { return list.contains(offset); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java index a884346842..8b2a03d4c5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java @@ -107,20 +107,6 @@ public abstract class DfsRefDatabase extends RefDatabase { /** {@inheritDoc} */ @Override - public Ref getRef(String needle) throws IOException { - RefCache curr = read(); - for (String prefix : SEARCH_PATH) { - Ref ref = curr.ids.get(prefix + needle); - if (ref != null) { - ref = resolve(ref, 0, curr.ids); - return ref; - } - } - return null; - } - - /** {@inheritDoc} */ - @Override public List<Ref> getAdditionalRefs() { return Collections.emptyList(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java index 7502471b0c..4853298012 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java @@ -128,7 +128,7 @@ public class DfsReftable extends BlockBasedFile { open().setReadAheadBytes(readAhead); } - DfsBlock block = cache.getOrLoad(file, pos, ctx, ch); + DfsBlock block = cache.getOrLoad(file, pos, ctx, () -> open()); if (block.start == pos && block.size() >= cnt) { return block.zeroCopyByteBuffer(cnt); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java index 70816307f5..83394bb92c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java @@ -99,6 +99,12 @@ public class DfsReftableDatabase extends DfsRefDatabase { /** {@inheritDoc} */ @Override + public boolean hasVersioning() { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean performsAtomicTransactions() { return true; } @@ -223,18 +229,6 @@ public class DfsReftableDatabase extends DfsRefDatabase { /** {@inheritDoc} */ @Override - public Ref getRef(String needle) throws IOException { - for (String prefix : SEARCH_PATH) { - Ref ref = exactRef(prefix + needle); - if (ref != null) { - return ref; - } - } - return null; - } - - /** {@inheritDoc} */ - @Override public Map<String, Ref> getRefs(String prefix) throws IOException { RefList.Builder<Ref> all = new RefList.Builder<>(); lock.lock(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 791a108289..7400308c86 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -160,7 +160,6 @@ public class GC { * * @param e * the executor to be used for running auto-gc - * @since 4.8 */ public static void setExecutor(ExecutorService e) { executor = e; @@ -918,7 +917,8 @@ public class GC { // Avoid deleting a folder that was created after the threshold so that concurrent // operations trying to create a reference are not impacted Instant threshold = Instant.now().minus(30, ChronoUnit.SECONDS); - try (Stream<Path> entries = Files.list(refs)) { + try (Stream<Path> entries = Files.list(refs) + .filter(Files::isDirectory)) { Iterator<Path> iterator = entries.iterator(); while (iterator.hasNext()) { try (Stream<Path> s = Files.list(iterator.next())) { 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 35522667e0..258cceebee 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 @@ -198,7 +198,6 @@ public class ObjectDirectory extends FileObjectDatabase { * <p>Getter for the field <code>packDirectory</code>.</p> * * @return the location of the <code>pack</code> directory. - * @since 4.10 */ public final File getPackDirectory() { return packDirectory; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java index de7e4b3f25..a4729bba48 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java @@ -74,6 +74,7 @@ import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -319,16 +320,14 @@ public class RefDirectory extends RefDatabase { return loose; } - /** {@inheritDoc} */ - @Override - public Ref exactRef(String name) throws IOException { - RefList<Ref> packed = getPackedRefs(); - Ref ref; + @Nullable + private Ref readAndResolve(String name, RefList<Ref> packed) throws IOException { try { - ref = readRef(name, packed); + Ref ref = readRef(name, packed); if (ref != null) { ref = resolve(ref, 0, null, null, packed); } + return ref; } catch (IOException e) { if (name.contains("/") //$NON-NLS-1$ || !(e.getCause() instanceof InvalidObjectIdException)) { @@ -338,35 +337,55 @@ public class RefDirectory extends RefDatabase { // While looking for a ref outside of refs/ (e.g., 'config'), we // found a non-ref file (e.g., a config file) instead. Treat this // as a ref-not-found condition. - ref = null; + return null; } - fireRefsChanged(); - return ref; } /** {@inheritDoc} */ @Override - public Ref getRef(String needle) throws IOException { - final RefList<Ref> packed = getPackedRefs(); - Ref ref = null; - for (String prefix : SEARCH_PATH) { - try { - ref = readRef(prefix + needle, packed); + public Ref exactRef(String name) throws IOException { + try { + return readAndResolve(name, getPackedRefs()); + } finally { + fireRefsChanged(); + } + } + + /** {@inheritDoc} */ + @Override + @NonNull + public Map<String, Ref> exactRef(String... refs) throws IOException { + try { + RefList<Ref> packed = getPackedRefs(); + Map<String, Ref> result = new HashMap<>(refs.length); + for (String name : refs) { + Ref ref = readAndResolve(name, packed); if (ref != null) { - ref = resolve(ref, 0, null, null, packed); + result.put(name, ref); } + } + return result; + } finally { + fireRefsChanged(); + } + } + + /** {@inheritDoc} */ + @Override + @Nullable + public Ref firstExactRef(String... refs) throws IOException { + try { + RefList<Ref> packed = getPackedRefs(); + for (String name : refs) { + Ref ref = readAndResolve(name, packed); if (ref != null) { - break; - } - } catch (IOException e) { - if (!(!needle.contains("/") && "".equals(prefix) && e //$NON-NLS-1$ //$NON-NLS-2$ - .getCause() instanceof InvalidObjectIdException)) { - throw e; + return ref; } } + return null; + } finally { + fireRefsChanged(); } - fireRefsChanged(); - return ref; } /** {@inheritDoc} */ @@ -414,7 +433,7 @@ public class RefDirectory extends RefDatabase { public List<Ref> getAdditionalRefs() throws IOException { List<Ref> ret = new LinkedList<>(); for (String name : additionalRefsNames) { - Ref r = getRef(name); + Ref r = exactRef(name); if (r != null) ret.add(r); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java index 45ce6349a5..1a0d6953ab 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java @@ -87,7 +87,7 @@ class RefDirectoryUpdate extends RefUpdate { String name = dst.getName(); lock = new LockFile(database.fileFor(name)); if (lock.lock()) { - dst = database.getRef(name); + dst = database.findRef(name); setOldObjectId(dst != null ? dst.getObjectId() : null); return true; } else { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java index 8cf1d4e219..e8fac514be 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java @@ -146,7 +146,7 @@ public class WindowCache { * Modify the configuration of the window cache. * <p> * The new configuration is applied immediately. If the new limits are - * smaller than what what is currently cached, older entries will be purged + * smaller than what is currently cached, older entries will be purged * as soon as possible to allow the cache to meet the new limit. * * @deprecated use {@code cfg.install()} to avoid internal reference. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java index 343faf4df4..cfc1ccd625 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaEncoder.java @@ -73,7 +73,7 @@ public class DeltaEncoder { /** Maximum number of bytes used by a copy instruction. */ private static final int MAX_COPY_CMD_SIZE = 8; - /** Maximum length that an an insert command can encode at once. */ + /** Maximum length that an insert command can encode at once. */ private static final int MAX_INSERT_DATA_SIZE = 127; private final OutputStream out; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 24af8a73ba..1e3d74ab57 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -642,7 +642,6 @@ public class PackWriter implements AutoCloseable { /** * @param bytes exclude blobs of size greater than this - * @since 5.0 */ public void setFilterBlobLimit(long bytes) { filterBlobLimit = bytes; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java index ce2ba4a2e1..44529bfff2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java @@ -170,24 +170,27 @@ class BlockReader { return readVarint64(); } - Ref readRef() throws IOException { + Ref readRef(long minUpdateIndex) throws IOException { + long updateIndex = minUpdateIndex + readUpdateIndexDelta(); String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen); switch (valueType & VALUE_TYPE_MASK) { case VALUE_NONE: // delete - return newRef(name); + return newRef(name, updateIndex); case VALUE_1ID: - return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId()); + return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId(), + updateIndex); case VALUE_2ID: { // annotated tag ObjectId id1 = readValueId(); ObjectId id2 = readValueId(); - return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2); + return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2, + updateIndex); } case VALUE_SYMREF: { String val = readValueString(); - return new SymbolicRef(name, newRef(val)); + return new SymbolicRef(name, newRef(val, updateIndex), updateIndex); } default: @@ -410,7 +413,7 @@ class BlockReader { * <ul> * <li>{@link #name()} * <li>{@link #match(byte[], boolean)} - * <li>{@link #readRef()} + * <li>{@link #readRef(long)} * <li>{@link #readLogUpdateIndex()} * <li>{@link #readLogEntry()} * <li>{@link #readBlockPositionList()} @@ -575,8 +578,8 @@ class BlockReader { return val; } - private static Ref newRef(String name) { - return new ObjectIdRef.Unpeeled(NEW, name, null); + private static Ref newRef(String name, long updateIndex) { + return new ObjectIdRef.Unpeeled(NEW, name, null, updateIndex); } private static IOException invalidBlock() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java index 17894b1664..c740bf2c37 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java @@ -168,7 +168,6 @@ public class MergedReftable extends Reftable { private final PriorityQueue<RefQueueEntry> queue; private RefQueueEntry head; private Ref ref; - private long updateIndex; MergedRefCursor() { queue = new PriorityQueue<>(queueSize(), RefQueueEntry::compare); @@ -206,7 +205,6 @@ public class MergedReftable extends Reftable { } ref = t.rc.getRef(); - updateIndex = t.rc.getUpdateIndex(); boolean include = includeDeletes || !t.rc.wasDeleted(); add(t); skipShadowedRefs(ref.getName()); @@ -242,11 +240,6 @@ public class MergedReftable extends Reftable { } @Override - public long getUpdateIndex() { - return updateIndex; - } - - @Override public void close() { if (head != null) { head.rc.close(); @@ -285,7 +278,7 @@ public class MergedReftable extends Reftable { } long updateIndex() { - return rc.getUpdateIndex(); + return rc.getRef().getUpdateIndex(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java index 5d4af30a91..9749ffb906 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java @@ -69,13 +69,6 @@ public abstract class RefCursor implements AutoCloseable { public abstract Ref getRef(); /** - * Get updateIndex that last modified the current reference. - * - * @return updateIndex that last modified the current reference. - */ - public abstract long getUpdateIndex(); - - /** * Whether the current reference was deleted. * * @return {@code true} if the current reference was deleted. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java index a1087e2023..cb02628e8d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java @@ -280,7 +280,7 @@ public abstract class Reftable implements AutoCloseable { if (dst == null) { return null; // claim it doesn't exist } - return new SymbolicRef(ref.getName(), dst); + return new SymbolicRef(ref.getName(), dst, ref.getUpdateIndex()); } /** {@inheritDoc} */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java index ed73a9efbd..c4e8f69fa4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java @@ -256,7 +256,7 @@ public class ReftableCompactor { private void mergeRefs(MergedReftable mr) throws IOException { try (RefCursor rc = mr.allRefs()) { while (rc.next()) { - writer.writeRef(rc.getRef(), rc.getUpdateIndex()); + writer.writeRef(rc.getRef(), rc.getRef().getUpdateIndex()); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java index 81b30e4cb9..bf3a9aeca0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java @@ -479,7 +479,6 @@ public class ReftableReader extends Reftable { private final boolean prefix; private Ref ref; - private long updateIndex; BlockReader block; RefCursorImpl(long scanEnd, byte[] match, boolean prefix) { @@ -508,8 +507,7 @@ public class ReftableReader extends Reftable { return false; } - updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); - ref = block.readRef(); + ref = block.readRef(minUpdateIndex); if (!includeDeletes && wasDeleted()) { continue; } @@ -523,11 +521,6 @@ public class ReftableReader extends Reftable { } @Override - public long getUpdateIndex() { - return updateIndex; - } - - @Override public void close() { // Do nothing. } @@ -605,7 +598,6 @@ public class ReftableReader extends Reftable { private final ObjectId match; private Ref ref; - private long updateIndex; private int listIdx; private LongList blockPos; @@ -679,8 +671,7 @@ public class ReftableReader extends Reftable { } block.parseKey(); - updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); - ref = block.readRef(); + ref = block.readRef(minUpdateIndex); ObjectId id = ref.getObjectId(); if (id != null && match.equals(id) && (includeDeletes || !wasDeleted())) { @@ -695,11 +686,6 @@ public class ReftableReader extends Reftable { } @Override - public long getUpdateIndex() { - return updateIndex; - } - - @Override public void close() { // Do nothing. } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java index 27daaf0bb2..ddd05b3e54 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java @@ -206,16 +206,6 @@ public class RefTreeDatabase extends RefDatabase { /** {@inheritDoc} */ @Override - public Ref getRef(String name) throws IOException { - String[] needle = new String[SEARCH_PATH.length]; - for (int i = 0; i < SEARCH_PATH.length; i++) { - needle[i] = SEARCH_PATH[i] + name; - } - return firstExactRef(needle); - } - - /** {@inheritDoc} */ - @Override public Ref exactRef(String name) throws IOException { if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) { // Pass through names like MERGE_HEAD, ORIG_HEAD, FETCH_HEAD. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java index 2ef0f20d8d..2fa59f3cd3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java @@ -221,10 +221,11 @@ class Scanner { return new SymbolicRef(ref.getName(), dst); } - @SuppressWarnings("resource") private static RevTree toTree(ObjectReader reader, AnyObjectId id) throws IOException { - return new RevWalk(reader).parseTree(id); + try (RevWalk rw = new RevWalk(reader)) { + return rw.parseTree(id); + } } private static boolean curElementHasPeelSuffix(AbstractTreeIterator itr) { 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 7b872b1860..cd6af6a127 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 @@ -61,7 +61,7 @@ import org.eclipse.jgit.lib.ObjectChecker; * Validations for the git submodule fields (name, path, uri). * * Invalid values in these fields can cause security problems as reported in - * CVE-2018-11235 and and CVE-2018-17456 + * CVE-2018-11235 and CVE-2018-17456 */ public class SubmoduleValidator { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java new file mode 100644 index 0000000000..0426b17f04 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java @@ -0,0 +1,108 @@ +/* + * 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 java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Collectors.toSet; + +import java.util.Set; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * In a push, the client sends a list of commands. The first command + * is special, as it can include a list of capabilities at its end. + * <p> + * For example: + * "oid oid name\0cap1 cap cap3" + * <p> + * Not to be confused with {@link FirstWant}, nor with the first line + * of the reference advertisement parsed by + * {@code BasePackConnection.readAdvertisedRefs}. + * <p> + * This class parses the inputted command line and holds the results: + * the actual command line and the capabilities. + */ +public final class FirstCommand { + private final String line; + private final Set<String> capabilities; + + /** + * Parse the first line of a receive-pack request. + * + * @param line + * line from the client. + * @return an instance of FirstCommand with capabilities parsed out + */ + @NonNull + public static FirstCommand fromLine(String line) { + int nul = line.indexOf('\0'); + if (nul < 0) { + return new FirstCommand(line, emptySet()); + } + Set<String> opts = + asList(line.substring(nul + 1).split(" ")) //$NON-NLS-1$ + .stream() + .collect(toSet()); + return new FirstCommand(line.substring(0, nul), unmodifiableSet(opts)); + } + + private FirstCommand(String line, Set<String> capabilities) { + this.line = line; + this.capabilities = capabilities; + } + + /** @return non-capabilities part of the line. */ + @NonNull + public String getLine() { + return line; + } + + /** @return capabilities parsed from the line, as an immutable set. */ + @NonNull + public Set<String> getCapabilities() { + return capabilities; + } +} 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 index 2dae021702..401c50776d 100644 --- 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 @@ -67,7 +67,6 @@ import org.eclipse.jgit.internal.JGitText; * 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java index c30833d0a6..6cbddec543 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java @@ -49,11 +49,15 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import java.text.MessageFormat; import java.util.List; +import org.eclipse.jgit.internal.JGitText; + /** * Mutable builder to construct a commit recording the state of a project. * @@ -76,6 +80,8 @@ public class CommitBuilder { private static final byte[] hcommitter = Constants.encodeASCII("committer"); //$NON-NLS-1$ + private static final byte[] hgpgsig = Constants.encodeASCII("gpgsig"); //$NON-NLS-1$ + private static final byte[] hencoding = Constants.encodeASCII("encoding"); //$NON-NLS-1$ private ObjectId treeId; @@ -86,6 +92,8 @@ public class CommitBuilder { private PersonIdent committer; + private GpgSignature gpgSignature; + private String message; private Charset encoding; @@ -108,7 +116,7 @@ public class CommitBuilder { } /** - * Set the tree id for this commit object + * Set the tree id for this commit object. * * @param id * the tree identity. @@ -146,7 +154,7 @@ public class CommitBuilder { } /** - * Set the committer and commit time for this object + * Set the committer and commit time for this object. * * @param newCommitter * the committer information. Should not be null. @@ -156,6 +164,38 @@ public class CommitBuilder { } /** + * Set the GPG signature of this commit. + * <p> + * Note, the signature set here will change the payload of the commit, i.e. + * the output of {@link #build()} will include the signature. Thus, the + * typical flow will be: + * <ol> + * <li>call {@link #build()} without a signature set to obtain payload</li> + * <li>create {@link GpgSignature} from payload</li> + * <li>set {@link GpgSignature}</li> + * </ol> + * </p> + * + * @param newSignature + * the signature to set or <code>null</code> to unset + * @since 5.3 + */ + public void setGpgSignature(GpgSignature newSignature) { + gpgSignature = newSignature; + } + + /** + * Get the GPG signature of this commit. + * + * @return the GPG signature of this commit, maybe <code>null</code> if the + * commit is not to be signed + * @since 5.3 + */ + public GpgSignature getGpgSignature() { + return gpgSignature; + } + + /** * Get the ancestors of this commit. * * @return the ancestors of this commit. Never null. @@ -250,18 +290,20 @@ public class CommitBuilder { } /** - * Set the encoding for the commit information + * Set the encoding for the commit information. * * @param encodingName * the encoding name. See * {@link java.nio.charset.Charset#forName(String)}. + * @deprecated use {@link #setEncoding(Charset)} instead. */ + @Deprecated public void setEncoding(String encodingName) { encoding = Charset.forName(encodingName); } /** - * Set the encoding for the commit information + * Set the encoding for the commit information. * * @param enc * the encoding to use. @@ -316,6 +358,13 @@ public class CommitBuilder { w.flush(); os.write('\n'); + if (getGpgSignature() != null) { + os.write(hgpgsig); + os.write(' '); + writeGpgSignatureString(getGpgSignature().toExternalString(), os); + os.write('\n'); + } + if (getEncoding() != UTF_8) { os.write(hencoding); os.write(' '); @@ -339,6 +388,50 @@ public class CommitBuilder { } /** + * Writes signature to output as per <a href= + * "https://github.com/git/git/blob/master/Documentation/technical/signature-format.txt#L66,L89">gpgsig + * header</a>. + * <p> + * CRLF and CR will be sanitized to LF and signature will have a hanging + * indent of one space starting with line two. + * </p> + * + * @param in + * signature string with line breaks + * @param out + * output stream + * @throws IOException + * thrown by the output stream + * @throws IllegalArgumentException + * if the signature string contains non 7-bit ASCII chars + */ + static void writeGpgSignatureString(String in, OutputStream out) + throws IOException, IllegalArgumentException { + for (int i = 0; i < in.length(); ++i) { + char ch = in.charAt(i); + if (ch == '\r') { + if (i + 1 < in.length() && in.charAt(i + 1) == '\n') { + out.write('\n'); + out.write(' '); + ++i; + } else { + out.write('\n'); + out.write(' '); + } + } else if (ch == '\n') { + out.write('\n'); + out.write(' '); + } else { + // sanity check + if (ch > 127) + throw new IllegalArgumentException(MessageFormat + .format(JGitText.get().notASCIIString, in)); + out.write(ch); + } + } + } + + /** * Format this builder's state as a commit object. * * @return this object in the canonical commit format, suitable for storage @@ -377,6 +470,10 @@ public class CommitBuilder { r.append(committer != null ? committer.toString() : "NOT_SET"); r.append("\n"); + r.append("gpgSignature "); + r.append(gpgSignature != null ? gpgSignature.toString() : "NOT_SET"); + r.append("\n"); + if (encoding != null && encoding != UTF_8) { r.append("encoding "); r.append(encoding.name()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java new file mode 100644 index 0000000000..663f850f0a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java @@ -0,0 +1,98 @@ +/* + * 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; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +import java.io.Serializable; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * A structure for holding GPG signature together with additional related data. + * + * @since 5.3 + */ +public class GpgSignature implements Serializable { + + private static final long serialVersionUID = 1L; + + private byte[] signature; + + /** + * Creates a new instance with the specified signature + * + * @param signature + * the signature + */ + public GpgSignature(@NonNull byte[] signature) { + this.signature = signature; + } + + /** + * Format for Git storage. + * <p> + * This returns the ASCII Armor as per + * https://tools.ietf.org/html/rfc4880#section-6.2. + * </p> + * + * @return a string of the signature ready to be embedded in a Git object + */ + public String toExternalString() { + return new String(signature, US_ASCII); + } + + /** {@inheritDoc} */ + @Override + @SuppressWarnings("nls") + public String toString() { + final StringBuilder r = new StringBuilder(); + + r.append("GpgSignature["); + r.append( + this.signature != null ? "length " + signature.length : "null"); + r.append("]"); + + return r.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java new file mode 100644 index 0000000000..99a23c6e42 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java @@ -0,0 +1,138 @@ +/* + * 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; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.lib.internal.BouncyCastleGpgSigner; +import org.eclipse.jgit.transport.CredentialsProvider; + +/** + * Creates GPG signatures for Git objects. + * + * @since 5.3 + */ +public abstract class GpgSigner { + + private static GpgSigner defaultSigner = new BouncyCastleGpgSigner(); + + /** + * Get the default signer, or <code>null</code>. + * + * @return the default signer, or <code>null</code>. + */ + public static GpgSigner getDefault() { + return defaultSigner; + } + + /** + * Set the default signer. + * + * @param signer + * the new default signer, may be <code>null</code> to select no + * default. + */ + public static void setDefault(GpgSigner signer) { + GpgSigner.defaultSigner = signer; + } + + /** + * Signs the specified commit. + * + * <p> + * Implementors should obtain the payload for signing from the specified + * commit via {@link CommitBuilder#build()} and create a proper + * {@link GpgSignature}. The generated signature must be set on the + * specified {@code commit} (see + * {@link CommitBuilder#setGpgSignature(GpgSignature)}). + * </p> + * <p> + * Any existing signature on the commit must be discarded prior obtaining + * the payload via {@link CommitBuilder#build()}. + * </p> + * + * @param commit + * the commit to sign (must not be <code>null</code> and must be + * complete to allow proper calculation of payload) + * @param gpgSigningKey + * the signing key to locate (passed as is to the GPG signing + * tool as is; eg., value of <code>user.signingkey</code>) + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + */ + public abstract void sign(@NonNull CommitBuilder commit, + @Nullable String gpgSigningKey, @NonNull PersonIdent committer, + CredentialsProvider credentialsProvider) throws CanceledException; + + /** + * Indicates if a signing key is available for the specified committer + * and/or signing key. + * + * @param gpgSigningKey + * the signing key to locate (passed as is to the GPG signing + * tool as is; eg., value of <code>user.signingkey</code>) + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @return <code>true</code> if a signing key is available, + * <code>false</code> otherwise + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + */ + public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey, + @NonNull PersonIdent committer, + CredentialsProvider credentialsProvider) throws CanceledException; + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java index 764f890158..0e96138c00 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java @@ -49,6 +49,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.RawParseUtils; @@ -86,7 +87,10 @@ public class ObjectId extends AnyObjectId implements Serializable { * the string to test. * @return true if the string can converted into an ObjectId. */ - public static final boolean isId(String id) { + public static final boolean isId(@Nullable String id) { + if (id == null) { + return false; + } if (id.length() != Constants.OBJECT_ID_STRING_LENGTH) return false; try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java index 22aaa3ad73..b791c64552 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java @@ -67,7 +67,26 @@ public abstract class ObjectIdRef implements Ref { */ public Unpeeled(@NonNull Storage st, @NonNull String name, @Nullable ObjectId id) { - super(st, name, id); + super(st, name, id, -1); + } + + /** + * Create a new ref pairing with update index. + * + * @param st + * method used to store this ref. + * @param name + * name of this ref. + * @param id + * current value of the ref. May be {@code null} to indicate + * a ref that does not exist yet. + * @param updateIndex + * number increasing with each update to the reference. + * @since 5.3 + */ + public Unpeeled(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id, long updateIndex) { + super(st, name, id, updateIndex); } @Override @@ -100,7 +119,29 @@ public abstract class ObjectIdRef implements Ref { */ public PeeledTag(@NonNull Storage st, @NonNull String name, @Nullable ObjectId id, @NonNull ObjectId p) { - super(st, name, id); + super(st, name, id, -1); + peeledObjectId = p; + } + + /** + * Create a new ref pairing with update index. + * + * @param st + * method used to store this ref. + * @param name + * name of this ref. + * @param id + * current value of the ref. May be {@code null} to indicate + * a ref that does not exist yet. + * @param p + * the first non-tag object that tag {@code id} points to. + * @param updateIndex + * number increasing with each update to the reference. + * @since 5.3 + */ + public PeeledTag(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id, @NonNull ObjectId p, long updateIndex) { + super(st, name, id, updateIndex); peeledObjectId = p; } @@ -131,7 +172,26 @@ public abstract class ObjectIdRef implements Ref { */ public PeeledNonTag(@NonNull Storage st, @NonNull String name, @Nullable ObjectId id) { - super(st, name, id); + super(st, name, id, -1); + } + + /** + * Create a new ref pairing with update index. + * + * @param st + * method used to store this ref. + * @param name + * name of this ref. + * @param id + * current value of the ref. May be {@code null} to indicate + * a ref that does not exist yet. + * @param updateIndex + * number increasing with each update to the reference. + * @since 5.3 + */ + public PeeledNonTag(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id, long updateIndex) { + super(st, name, id, updateIndex); } @Override @@ -152,6 +212,8 @@ public abstract class ObjectIdRef implements Ref { private final ObjectId objectId; + private final long updateIndex; + /** * Create a new ref pairing. * @@ -162,12 +224,17 @@ public abstract class ObjectIdRef implements Ref { * @param id * current value of the ref. May be {@code null} to indicate a * ref that does not exist yet. + * @param updateIndex + * number that increases with each ref update. Set to -1 if the + * storage doesn't support versioning. + * @since 5.3 */ protected ObjectIdRef(@NonNull Storage st, @NonNull String name, - @Nullable ObjectId id) { + @Nullable ObjectId id, long updateIndex) { this.name = name; this.storage = st; this.objectId = id; + this.updateIndex = updateIndex; } /** {@inheritDoc} */ @@ -211,6 +278,18 @@ public abstract class ObjectIdRef implements Ref { return storage; } + /** + * {@inheritDoc} + * @since 5.3 + */ + @Override + public long getUpdateIndex() { + if (updateIndex == -1) { + throw new UnsupportedOperationException(); + } + return updateIndex; + } + /** {@inheritDoc} */ @NonNull @Override @@ -220,7 +299,9 @@ public abstract class ObjectIdRef implements Ref { r.append(getName()); r.append('='); r.append(ObjectId.toString(getObjectId())); - r.append(']'); + r.append('('); + r.append(updateIndex); // Print value, even if -1 + r.append(")]"); //$NON-NLS-1$ return r.toString(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java index faabbf892f..32c8b06c91 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java @@ -217,4 +217,27 @@ public interface Ref { */ @NonNull Storage getStorage(); + + /** + * Indicator of the relative order between updates of a specific reference + * name. A number that increases when a reference is updated. + * <p> + * With symbolic references, the update index refers to updates of the + * symbolic reference itself. For example, if HEAD points to + * refs/heads/master, then the update index for exactRef("HEAD") will only + * increase when HEAD changes to point to another ref, regardless of how + * many times refs/heads/master is updated. + * <p> + * Should not be used unless the {@code RefDatabase} that instantiated the + * ref supports versioning (see {@link RefDatabase#hasVersioning()}) + * + * @return the update index (i.e. version) of this reference. + * @throws UnsupportedOperationException + * if the creator of the instance (e.g. {@link RefDatabase}) + * doesn't support versioning and doesn't override this method + * @since 5.3 + */ + default long getUpdateIndex() { + throw new UnsupportedOperationException(); + } } 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 68929b4220..877792097c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -69,10 +69,10 @@ public abstract class RefDatabase { /** * Order of prefixes to search when using non-absolute references. * <p> - * The implementation's {@link #getRef(String)} method must take this search - * space into consideration when locating a reference by name. The first - * entry in the path is always {@code ""}, ensuring that absolute references - * are resolved without further mangling. + * {@link #findRef(String)} takes this search space into consideration + * when locating a reference by name. The first entry in the path is + * always {@code ""}, ensuring that absolute references are resolved + * without further mangling. */ protected static final String[] SEARCH_PATH = { "", //$NON-NLS-1$ Constants.R_REFS, // @@ -111,6 +111,19 @@ public abstract class RefDatabase { public abstract void close(); /** + * With versioning, each reference has a version number that increases on + * update. See {@link Ref#getUpdateIndex()}. + * + * @implSpec This method returns false by default. Implementations + * supporting versioning must override it to return true. + * @return true if the implementation assigns update indices to references. + * @since 5.3 + */ + public boolean hasVersioning() { + return false; + } + + /** * Determine if a proposed reference name overlaps with an existing one. * <p> * Reference names use '/' as a component separator, and may be stored in a @@ -244,6 +257,23 @@ public abstract class RefDatabase { } /** + * Compatibility synonym for {@link #findRef(String)}. + * + * @param name + * the name of the reference. May be a short name which must be + * searched for using the standard {@link #SEARCH_PATH}. + * @return the reference (if it exists); else {@code null}. + * @throws IOException + * the reference space cannot be accessed. + * @deprecated Use {@link #findRef(String)} instead. + */ + @Deprecated + @Nullable + public final Ref getRef(String name) throws IOException { + return findRef(name); + } + + /** * Read a single reference. * <p> * Aside from taking advantage of {@link #SEARCH_PATH}, this method may be @@ -259,14 +289,21 @@ public abstract class RefDatabase { * @return the reference (if it exists); else {@code null}. * @throws java.io.IOException * the reference space cannot be accessed. + * @since 5.3 */ @Nullable - public abstract Ref getRef(String name) throws IOException; + public final Ref findRef(String name) throws IOException { + String[] names = new String[SEARCH_PATH.length]; + for (int i = 0; i < SEARCH_PATH.length; i++) { + names[i] = SEARCH_PATH[i] + name; + } + return firstExactRef(names); + } /** * Read a single reference. * <p> - * Unlike {@link #getRef}, this method expects an unshortened reference + * Unlike {@link #findRef}, this method expects an unshortened reference * name and does not search using the standard {@link #SEARCH_PATH}. * * @param name @@ -277,13 +314,7 @@ public abstract class RefDatabase { * @since 4.1 */ @Nullable - public Ref exactRef(String name) throws IOException { - Ref ref = getRef(name); - if (ref == null || !name.equals(ref.getName())) { - return null; - } - return ref; - } + public abstract Ref exactRef(String name) throws IOException; /** * Read the specified references. @@ -462,7 +493,7 @@ public abstract class RefDatabase { * <p> * The result list includes non-ref items such as MERGE_HEAD and * FETCH_RESULT cast to be refs. The names of these refs are not returned by - * <code>getRefs()</code> but are accepted by {@link #getRef(String)} + * <code>getRefs()</code> but are accepted by {@link #findRef(String)} * and {@link #exactRef(String)}. * * @return a list of additional refs diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java index a05daa00ab..0bd34b51ac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java @@ -184,7 +184,7 @@ public abstract class RefRename { * the current value of {@code HEAD} cannot be read. */ protected boolean needToUpdateHEAD() throws IOException { - Ref head = source.getRefDatabase().getRef(Constants.HEAD); + Ref head = source.getRefDatabase().exactRef(Constants.HEAD); if (head != null && head.isSymbolic()) { head = head.getTarget(); return head.getName().equals(source.getName()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java index fc3ea8467a..1ce1528344 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java @@ -665,7 +665,7 @@ public abstract class RefUpdate { : getRef().getLeaf().getName(); if (myName.startsWith(Constants.R_HEADS) && !getRepository().isBare()) { // Don't allow the currently checked out branch to be deleted. - Ref head = getRefDatabase().getRef(Constants.HEAD); + Ref head = getRefDatabase().exactRef(Constants.HEAD); while (head != null && head.isSymbolic()) { head = head.getTarget(); if (myName.equals(head.getName())) @@ -708,7 +708,7 @@ public abstract class RefUpdate { if (!tryLock(false)) return Result.LOCK_FAILURE; - final Ref old = getRefDatabase().getRef(getName()); + final Ref old = getRefDatabase().exactRef(getName()); if (old != null && old.isSymbolic()) { final Ref dst = old.getTarget(); if (target.equals(dst.getName())) @@ -718,7 +718,7 @@ public abstract class RefUpdate { if (old != null && old.getObjectId() != null) setOldObjectId(old.getObjectId()); - final Ref dst = getRefDatabase().getRef(target); + final Ref dst = getRefDatabase().exactRef(target); if (dst != null && dst.getObjectId() != null) setNewObjectId(dst.getObjectId()); 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 77d268a3bd..a61897a652 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -57,6 +57,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.Collection; @@ -297,7 +298,7 @@ public abstract class Repository implements AutoCloseable { /** * Get the used file system abstraction. * - * @return the used file system abstraction, or or {@code null} if + * @return the used file system abstraction, or {@code null} if * repository isn't local. */ /* @@ -319,13 +320,14 @@ public abstract class Repository implements AutoCloseable { * a {@link org.eclipse.jgit.lib.AnyObjectId} object. * @return true if the specified object is stored in this repo or any of the * known shared repositories. + * @deprecated use {@code getObjectDatabase().has(objectId)} */ + @Deprecated public boolean hasObject(AnyObjectId objectId) { try { return getObjectDatabase().has(objectId); } catch (IOException e) { - // Legacy API, assume error means "no" - return false; + throw new UncheckedIOException(e); } } @@ -849,7 +851,7 @@ public abstract class Repository implements AutoCloseable { return ObjectId.fromString(revstr); if (Repository.isValidRefName("x/" + revstr)) { //$NON-NLS-1$ - Ref r = getRefDatabase().getRef(revstr); + Ref r = getRefDatabase().findRef(revstr); if (r != null) return r.getObjectId(); } @@ -1079,7 +1081,7 @@ public abstract class Repository implements AutoCloseable { * * @param name * the name of the ref to lookup. May be a short-hand form, e.g. - * "master" which is is automatically expanded to + * "master" which is automatically expanded to * "refs/heads/master" if "refs/heads/master" already exists. * @return the Ref with the given name, or {@code null} if it does not exist * @throws java.io.IOException @@ -1087,7 +1089,7 @@ public abstract class Repository implements AutoCloseable { */ @Nullable public final Ref findRef(String name) throws IOException { - return getRefDatabase().getRef(name); + return getRefDatabase().findRef(name); } /** @@ -1103,7 +1105,7 @@ public abstract class Repository implements AutoCloseable { try { return getRefDatabase().getRefs(RefDatabase.ALL); } catch (IOException e) { - return new HashMap<>(); + throw new UncheckedIOException(e); } } @@ -1121,7 +1123,7 @@ public abstract class Repository implements AutoCloseable { try { return getRefDatabase().getRefs(Constants.R_TAGS); } catch (IOException e) { - return new HashMap<>(); + throw new UncheckedIOException(e); } } @@ -1320,9 +1322,7 @@ public abstract class Repository implements AutoCloseable { return RepositoryState.MERGING_RESOLVED; } } catch (IOException e) { - // Can't decide whether unmerged paths exists. Return - // MERGING state to be on the safe side (in state MERGING - // you are not allow to do anything) + throw new UncheckedIOException(e); } return RepositoryState.MERGING; } @@ -1337,7 +1337,7 @@ public abstract class Repository implements AutoCloseable { return RepositoryState.CHERRY_PICKING_RESOLVED; } } catch (IOException e) { - // fall through to CHERRY_PICKING + throw new UncheckedIOException(e); } return RepositoryState.CHERRY_PICKING; @@ -1350,7 +1350,7 @@ public abstract class Repository implements AutoCloseable { return RepositoryState.REVERTING_RESOLVED; } } catch (IOException e) { - // fall through to REVERTING + throw new UncheckedIOException(e); } return RepositoryState.REVERTING; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java index d4b83b0128..00fcf52037 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java @@ -58,6 +58,8 @@ public class SymbolicRef implements Ref { private final Ref target; + private final long updateIndex; + /** * Create a new ref pairing. * @@ -69,6 +71,25 @@ public class SymbolicRef implements Ref { public SymbolicRef(@NonNull String refName, @NonNull Ref target) { this.name = refName; this.target = target; + this.updateIndex = -1; + } + + /** + * Create a new ref pairing. + * + * @param refName + * name of this ref. + * @param target + * the ref we reference and derive our value from. + * @param updateIndex + * index that increases with each update of the reference + * @since 5.3 + */ + public SymbolicRef(@NonNull String refName, @NonNull Ref target, + long updateIndex) { + this.name = refName; + this.target = target; + this.updateIndex = updateIndex; } /** {@inheritDoc} */ @@ -128,6 +149,18 @@ public class SymbolicRef implements Ref { return getLeaf().isPeeled(); } + /** + * {@inheritDoc} + * @since 5.3 + */ + @Override + public long getUpdateIndex() { + if (updateIndex == -1) { + throw new UnsupportedOperationException(); + } + return updateIndex; + } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override @@ -143,7 +176,9 @@ public class SymbolicRef implements Ref { r.append(cur.getName()); r.append('='); r.append(ObjectId.toString(cur.getObjectId())); - r.append("]"); + r.append("("); + r.append(updateIndex); // Print value, even if -1 + r.append(")]"); return r.toString(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java new file mode 100644 index 0000000000..ef9d7ee393 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKey.java @@ -0,0 +1,71 @@ +/* + * 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.internal; + +import java.nio.file.Path; + +import org.bouncycastle.openpgp.PGPSecretKey; + +/** + * Container which holds a {@link #getSecretKey()} together with the + * {@link #getOrigin() path it was loaded from}. + */ +class BouncyCastleGpgKey { + + private PGPSecretKey secretKey; + + private Path origin; + + public BouncyCastleGpgKey(PGPSecretKey secretKey, Path origin) { + this.secretKey = secretKey; + this.origin = origin; + } + + public PGPSecretKey getSecretKey() { + return secretKey; + } + + public Path getOrigin() { + return origin; + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java new file mode 100644 index 0000000000..091667db01 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java @@ -0,0 +1,373 @@ +/* + * 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.internal; + +import static java.nio.file.Files.exists; +import static java.nio.file.Files.newInputStream; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.bouncycastle.gpg.SExprParser; +import org.bouncycastle.gpg.keybox.BlobType; +import org.bouncycastle.gpg.keybox.KeyBlob; +import org.bouncycastle.gpg.keybox.KeyBox; +import org.bouncycastle.gpg.keybox.KeyInformation; +import org.bouncycastle.gpg.keybox.PublicKeyRingBlob; +import org.bouncycastle.gpg.keybox.UserID; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.SystemReader; + +/** + * Locates GPG keys from either <code>~/.gnupg/private-keys-v1.d</code> or + * <code>~/.gnupg/secring.gpg</code> + */ +class BouncyCastleGpgKeyLocator { + + private static final Path GPG_DIRECTORY = findGpgDirectory(); + + private static final Path USER_KEYBOX_PATH = GPG_DIRECTORY + .resolve("pubring.kbx"); //$NON-NLS-1$ + + private static final Path USER_SECRET_KEY_DIR = GPG_DIRECTORY + .resolve("private-keys-v1.d"); //$NON-NLS-1$ + + private static final Path USER_PGP_LEGACY_SECRING_FILE = GPG_DIRECTORY + .resolve("secring.gpg"); //$NON-NLS-1$ + + private final String signingKey; + + private BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt; + + private static Path findGpgDirectory() { + SystemReader system = SystemReader.getInstance(); + if (system.isWindows()) { + // On Windows prefer %APPDATA%\gnupg if it exists, even if Cygwin is + // used. + String appData = system.getenv("APPDATA"); //$NON-NLS-1$ + if (appData != null && !appData.isEmpty()) { + try { + Path directory = Paths.get(appData).resolve("gnupg"); //$NON-NLS-1$ + if (Files.isDirectory(directory)) { + return directory; + } + } catch (SecurityException | InvalidPathException e) { + // Ignore and return the default location below. + } + } + } + // All systems, including Cygwin and even Windows if + // %APPDATA%\gnupg doesn't exist: ~/.gnupg + File home = FS.DETECTED.userHome(); + if (home == null) { + // Oops. What now? + home = new File(".").getAbsoluteFile(); //$NON-NLS-1$ + } + return home.toPath().resolve(".gnupg"); //$NON-NLS-1$ + } + + /** + * Create a new key locator for the specified signing key. + * <p> + * The signing key must either be a hex representation of a specific key or + * a user identity substring (eg., email address). All keys in the KeyBox + * will be looked up in the order as returned by the KeyBox. A key id will + * be searched before attempting to find a key by user id. + * </p> + * + * @param signingKey + * the signing key to search for + * @param passphrasePrompt + * the provider to use when asking for key passphrase + */ + public BouncyCastleGpgKeyLocator(String signingKey, + @NonNull BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt) { + this.signingKey = signingKey; + this.passphrasePrompt = passphrasePrompt; + } + + private PGPSecretKey attemptParseSecretKey(Path keyFile, + PGPDigestCalculatorProvider calculatorProvider, + PBEProtectionRemoverFactory passphraseProvider, + PGPPublicKey publicKey) throws IOException { + try (InputStream in = newInputStream(keyFile)) { + return new SExprParser(calculatorProvider).parseSecretKey( + new BufferedInputStream(in), passphraseProvider, publicKey); + } catch (PGPException | ClassCastException e) { + return null; + } + } + + private boolean containsSigningKey(String userId) { + return userId.toLowerCase(Locale.ROOT) + .contains(signingKey.toLowerCase(Locale.ROOT)); + } + + private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob) + throws IOException { + for (KeyInformation keyInfo : keyBlob.getKeyInformation()) { + if (signingKey.toLowerCase(Locale.ROOT) + .equals(Hex.toHexString(keyInfo.getKeyID()) + .toLowerCase(Locale.ROOT))) { + return getFirstPublicKey(keyBlob); + } + } + return null; + } + + private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob) + throws IOException { + for (UserID userID : keyBlob.getUserIds()) { + if (containsSigningKey(userID.getUserIDAsString())) { + return getFirstPublicKey(keyBlob); + } + } + return null; + } + + /** + * Finds a public key associated with the signing key. + * + * @param keyboxFile + * the KeyBox file + * @return publicKey the public key (maybe <code>null</code>) + * @throws IOException + * in case of problems reading the file + */ + private PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile) + throws IOException { + KeyBox keyBox = readKeyBoxFile(keyboxFile); + for (KeyBlob keyBlob : keyBox.getKeyBlobs()) { + if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) { + PGPPublicKey key = findPublicKeyByKeyId(keyBlob); + if (key != null) { + return key; + } + key = findPublicKeyByUserId(keyBlob); + if (key != null) { + return key; + } + } + } + return null; + } + + /** + * Use pubring.kbx when available, if not fallback to secring.gpg or secret + * key path provided to parse and return secret key + * + * @return the secret key + * @throws IOException + * in case of issues reading key files + * @throws PGPException + * in case of issues finding a key + * @throws CanceledException + * @throws URISyntaxException + * @throws UnsupportedCredentialItem + */ + public BouncyCastleGpgKey findSecretKey() + throws IOException, PGPException, CanceledException, + UnsupportedCredentialItem, URISyntaxException { + if (exists(USER_KEYBOX_PATH)) { + PGPPublicKey publicKey = // + findPublicKeyInKeyBox(USER_KEYBOX_PATH); + + if (publicKey != null) { + return findSecretKeyForKeyBoxPublicKey(publicKey, + USER_KEYBOX_PATH); + } + + throw new PGPException(MessageFormat + .format(JGitText.get().gpgNoPublicKeyFound, signingKey)); + } else if (exists(USER_PGP_LEGACY_SECRING_FILE)) { + PGPSecretKey secretKey = findSecretKeyInLegacySecring(signingKey, + USER_PGP_LEGACY_SECRING_FILE); + + if (secretKey != null) { + return new BouncyCastleGpgKey(secretKey, USER_PGP_LEGACY_SECRING_FILE); + } + + throw new PGPException(MessageFormat.format( + JGitText.get().gpgNoKeyInLegacySecring, signingKey)); + } + + throw new PGPException(JGitText.get().gpgNoKeyring); + } + + private BouncyCastleGpgKey findSecretKeyForKeyBoxPublicKey( + PGPPublicKey publicKey, Path userKeyboxPath) + throws PGPException, CanceledException, UnsupportedCredentialItem, + URISyntaxException { + /* + * this is somewhat brute-force but there doesn't seem to be another + * way; we have to walk all private key files we find and try to open + * them + */ + + PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() + .build(); + + PBEProtectionRemoverFactory passphraseProvider = new JcePBEProtectionRemoverFactory( + passphrasePrompt.getPassphrase(publicKey.getFingerprint(), + userKeyboxPath)); + + try (Stream<Path> keyFiles = Files.walk(USER_SECRET_KEY_DIR)) { + for (Path keyFile : keyFiles.filter(Files::isRegularFile) + .collect(Collectors.toList())) { + PGPSecretKey secretKey = attemptParseSecretKey(keyFile, + calculatorProvider, passphraseProvider, publicKey); + if (secretKey != null) { + return new BouncyCastleGpgKey(secretKey, userKeyboxPath); + } + } + + passphrasePrompt.clear(); + throw new PGPException(MessageFormat.format( + JGitText.get().gpgNoSecretKeyForPublicKey, + Long.toHexString(publicKey.getKeyID()))); + } catch (RuntimeException e) { + passphrasePrompt.clear(); + throw e; + } catch (IOException e) { + passphrasePrompt.clear(); + throw new PGPException(MessageFormat.format( + JGitText.get().gpgFailedToParseSecretKey, + USER_SECRET_KEY_DIR.toAbsolutePath()), e); + } + } + + /** + * Return the first suitable key for signing in the key ring collection. For + * this case we only expect there to be one key available for signing. + * </p> + * + * @param signingkey + * @param secringFile + * + * @return the first suitable PGP secret key found for signing + * @throws IOException + * on I/O related errors + * @throws PGPException + * on BouncyCastle errors + */ + private PGPSecretKey findSecretKeyInLegacySecring(String signingkey, + Path secringFile) throws IOException, PGPException { + + try (InputStream in = newInputStream(secringFile)) { + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( + PGPUtil.getDecoderStream(new BufferedInputStream(in)), + new JcaKeyFingerprintCalculator()); + + Iterator<PGPSecretKeyRing> keyrings = pgpSec.getKeyRings(); + while (keyrings.hasNext()) { + PGPSecretKeyRing keyRing = keyrings.next(); + Iterator<PGPSecretKey> keys = keyRing.getSecretKeys(); + while (keys.hasNext()) { + PGPSecretKey key = keys.next(); + // try key id + String fingerprint = Hex + .toHexString(key.getPublicKey().getFingerprint()) + .toLowerCase(Locale.ROOT); + if (fingerprint + .endsWith(signingkey.toLowerCase(Locale.ROOT))) { + return key; + } + // try user id + Iterator<String> userIDs = key.getUserIDs(); + while (userIDs.hasNext()) { + String userId = userIDs.next(); + if (containsSigningKey(userId)) { + return key; + } + } + } + } + } + return null; + } + + private PGPPublicKey getFirstPublicKey(KeyBlob keyBlob) throws IOException { + return ((PublicKeyRingBlob) keyBlob).getPGPPublicKeyRing() + .getPublicKey(); + } + + private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException { + KeyBox keyBox; + try (InputStream in = new BufferedInputStream( + newInputStream(keyboxFile))) { + // note: KeyBox constructor reads in the whole InputStream at once + // this code will change in 1.61 to + // either 'new BcKeyBox(in)' or 'new JcaKeyBoxBuilder().build(in)' + keyBox = new KeyBox(in, new JcaKeyFingerprintCalculator()); + } + return keyBox; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java new file mode 100644 index 0000000000..2efe962918 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyPassphrasePrompt.java @@ -0,0 +1,134 @@ +/*- + * Copyright (C) 2019, 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.internal; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.text.MessageFormat; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.transport.CredentialItem.CharArrayType; +import org.eclipse.jgit.transport.CredentialItem.InformationalMessage; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.URIish; + +/** + * Prompts for a passphrase and caches it until {@link #clear() cleared}. + * <p> + * Implements {@link AutoCloseable} so it can be used within a + * try-with-resources block. + * </p> + */ +class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable { + + private CharArrayType passphrase; + + private CredentialsProvider credentialsProvider; + + public BouncyCastleGpgKeyPassphrasePrompt( + CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } + + /** + * Clears any cached passphrase + */ + public void clear() { + if (passphrase != null) { + passphrase.clear(); + passphrase = null; + } + } + + @Override + public void close() { + clear(); + } + + private URIish createURI(Path keyLocation) throws URISyntaxException { + return new URIish(keyLocation.toUri().toString()); + } + + /** + * Prompts use for a passphrase unless one was cached from a previous + * prompt. + * + * @param keyFingerprint + * the fingerprint to show to the user during prompting + * @param keyLocation + * the location the key was loaded from + * @return the passphrase (maybe <code>null</code>) + * @throws PGPException + * @throws CanceledException + * in case passphrase was not entered by user + * @throws URISyntaxException + * @throws UnsupportedCredentialItem + */ + public char[] getPassphrase(byte[] keyFingerprint, Path keyLocation) + throws PGPException, CanceledException, UnsupportedCredentialItem, + URISyntaxException { + if (passphrase == null) { + passphrase = new CharArrayType(JGitText.get().credentialPassphrase, + true); + } + + if (credentialsProvider == null) { + throw new PGPException(JGitText.get().gpgNoCredentialsProvider); + } + + if (passphrase.getValue() == null + && !credentialsProvider.get(createURI(keyLocation), + new InformationalMessage( + MessageFormat.format(JGitText.get().gpgKeyInfo, + Hex.toHexString(keyFingerprint))), + passphrase)) { + throw new CanceledException(JGitText.get().gpgSigningCancelled); + } + return passphrase.getValue(); + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java new file mode 100644 index 0000000000..4d696dd9e7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java @@ -0,0 +1,160 @@ +/* + * 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.internal; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.Security; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.GpgSignature; +import org.eclipse.jgit.lib.GpgSigner; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.transport.CredentialsProvider; + +/** + * GPG Signer using BouncyCastle library + */ +public class BouncyCastleGpgSigner extends GpgSigner { + + private static void registerBouncyCastleProviderIfNecessary() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + /** + * Create a new instance. + * <p> + * The BounceCastleProvider will be registered if necessary. + * </p> + */ + public BouncyCastleGpgSigner() { + registerBouncyCastleProviderIfNecessary(); + } + + @Override + public boolean canLocateSigningKey(@Nullable String gpgSigningKey, + PersonIdent committer, CredentialsProvider credentialsProvider) + throws CanceledException { + try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( + credentialsProvider)) { + BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, + committer, passphrasePrompt); + return gpgKey != null; + } catch (PGPException | IOException | URISyntaxException e) { + return false; + } + } + + private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey, + PersonIdent committer, + BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt) + throws CanceledException, UnsupportedCredentialItem, IOException, + PGPException, URISyntaxException { + if (gpgSigningKey == null || gpgSigningKey.isEmpty()) { + gpgSigningKey = committer.getEmailAddress(); + } + + BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator( + gpgSigningKey, passphrasePrompt); + + return keyHelper.findSecretKey(); + } + + @Override + public void sign(@NonNull CommitBuilder commit, + @Nullable String gpgSigningKey, @NonNull PersonIdent committer, + CredentialsProvider credentialsProvider) throws CanceledException { + try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( + credentialsProvider)) { + BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, + committer, passphrasePrompt); + PGPSecretKey secretKey = gpgKey.getSecretKey(); + if (secretKey == null) { + throw new JGitInternalException( + JGitText.get().unableToSignCommitNoSecretKey); + } + char[] passphrase = passphrasePrompt.getPassphrase( + secretKey.getPublicKey().getFingerprint(), + gpgKey.getOrigin()); + PGPPrivateKey privateKey = secretKey + .extractPrivateKey(new JcePBESecretKeyDecryptorBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME) + .build(passphrase)); + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( + new JcaPGPContentSignerBuilder( + secretKey.getPublicKey().getAlgorithm(), + HashAlgorithmTags.SHA256).setProvider( + BouncyCastleProvider.PROVIDER_NAME)); + signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (BCPGOutputStream out = new BCPGOutputStream( + new ArmoredOutputStream(buffer))) { + signatureGenerator.update(commit.build()); + signatureGenerator.generate().encode(out); + } + commit.setGpgSignature(new GpgSignature(buffer.toByteArray())); + } catch (PGPException | IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java index 062d86f8a2..a533bf5653 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java @@ -119,7 +119,7 @@ public class MergeResult<S extends Sequence> implements Iterable<MergeChunk> { /** * Returns the common predecessor sequence and the merged sequence in one - * list. The common predecessor is is the first element in the list + * list. The common predecessor is the first element in the list * * @return the common predecessor at position 0 followed by the merged * sequences. 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 909f3b15d8..75334ddb0c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -1027,13 +1027,16 @@ public class ResolveMerger extends ThreeWayMerger { throws IOException { TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile( db != null ? nonNullRepo().getDirectory() : null, inCoreLimit); + boolean success = false; try { new MergeFormatter().formatMerge(buf, result, Arrays.asList(commitNames), UTF_8); buf.close(); - } catch (IOException e) { - buf.destroy(); - throw e; + success = true; + } finally { + if (!success) { + buf.destroy(); + } } return buf; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java index 89a87af9eb..375cd32041 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java @@ -79,7 +79,7 @@ public class NLS { /** * Sets the locale for the calling thread. * <p> - * The {@link #getBundleFor(Class)} method will honor this setting if if it + * The {@link #getBundleFor(Class)} method will honor this setting if it * is supported by the provided resource bundle property files. Otherwise, * it will use a fall back locale as described in the * {@link TranslationBundle} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java index 5e153164ad..45508ce027 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java @@ -141,7 +141,8 @@ public class PlotCommitList<L extends PlotLane> extends final PlotCommit<L> c = currCommit.children[0]; currCommit.lane = c.lane; Integer len = laneLength.get(currCommit.lane); - len = Integer.valueOf(len.intValue() + 1); + len = len != null ? Integer.valueOf(len.intValue() + 1) + : Integer.valueOf(0); laneLength.put(currCommit.lane, len); } else { // More than one child, or our child is a merge. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java index e5903c9117..fd578da333 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java @@ -330,11 +330,11 @@ public class ObjectWalk extends RevWalk { * * @return next most recent object; null if traversal is over. * @throws org.eclipse.jgit.errors.MissingObjectException - * one or or more of the next objects are not available from the + * one or more of the next objects are not available from the * object database, but were thought to be candidates for * traversal. This usually indicates a broken link. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException - * one or or more of the objects in a tree do not match the type + * one or more of the objects in a tree do not match the type * indicated. * @throws java.io.IOException * a pack file or loose object could not be read. @@ -534,11 +534,11 @@ public class ObjectWalk extends RevWalk { * provides some detail about the connectivity failure. * * @throws org.eclipse.jgit.errors.MissingObjectException - * one or or more of the next objects are not available from the + * one or more of the next objects are not available from the * object database, but were thought to be candidates for * traversal. This usually indicates a broken link. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException - * one or or more of the objects in a tree do not match the type + * one or more of the objects in a tree do not match the type * indicated. * @throws java.io.IOException * a pack file or loose object could not be read. 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 400ea33c21..0a43e8fb1a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -394,11 +394,11 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable { * <code>base</code> (and thus <code>base</code> is fully merged * into <code>tip</code>); false otherwise. * @throws org.eclipse.jgit.errors.MissingObjectException - * one or or more of the next commit's parents are not available + * one or more of the next commit's parents are not available * from the object database, but were thought to be candidates * for traversal. This usually indicates a broken link. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException - * one or or more of the next commit's parents are not actually + * one or more of the next commit's parents are not actually * commit objects. * @throws java.io.IOException * a pack file or loose object could not be read. @@ -431,11 +431,11 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable { * * @return next most recent commit; null if traversal is over. * @throws org.eclipse.jgit.errors.MissingObjectException - * one or or more of the next commit's parents are not available + * one or more of the next commit's parents are not available * from the object database, but were thought to be candidates * for traversal. This usually indicates a broken link. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException - * one or or more of the next commit's parents are not actually + * one or more of the next commit's parents are not actually * commit objects. * @throws java.io.IOException * a pack file or loose object could not be read. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java index c2e6a42001..ff499764c9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java @@ -251,7 +251,7 @@ public class WindowCacheConfig { * Install this configuration as the live settings. * <p> * The new configuration is applied immediately. If the new limits are - * smaller than what what is currently cached, older entries will be purged + * smaller than what is currently cached, older entries will be purged * as soon as possible to allow the cache to meet the new limit. * * @since 3.0 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java index 6bd32dd873..6722e9bdcd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -1100,7 +1100,7 @@ public class PackConfig { } /** - * Get the the age in days that marks a branch as "inactive". + * Get the age in days that marks a branch as "inactive". * * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS} * @@ -1112,7 +1112,7 @@ public class PackConfig { } /** - * Set the the age in days that marks a branch as "inactive". + * Set the age in days that marks a branch as "inactive". * * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS} * 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 72b4255df9..8512f2d9fe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java @@ -53,7 +53,7 @@ public interface AdvertiseRefsHook { * <p> * The method implementations do nothing to preserve the default behavior; see * {@link UploadPack#setAdvertisedRefs(java.util.Map)} and - * {@link BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}. + * {@link ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}. */ AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() { @Override @@ -85,7 +85,7 @@ public interface AdvertiseRefsHook { * * @param receivePack * instance on which to call - * {@link org.eclipse.jgit.transport.BaseReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} + * {@link org.eclipse.jgit.transport.ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)} * if necessary. * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException * abort; the message will be sent to the user. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java index 4ef3e1a974..12238a1f77 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java @@ -52,7 +52,7 @@ import java.util.List; * Hooks are run in the order passed to the constructor. A hook may inspect or * modify the results of the previous hooks in the chain by calling * {@link org.eclipse.jgit.transport.UploadPack#getAdvertisedRefs()}, or - * {@link org.eclipse.jgit.transport.BaseReceivePack#getAdvertisedRefs()} or + * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedRefs()} or * {@link org.eclipse.jgit.transport.BaseReceivePack#getAdvertisedObjects()}. */ public class AdvertiseRefsHookChain implements AdvertiseRefsHook { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java index 69624ff7a3..847e901980 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -338,7 +338,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen for (Ref r : getRefs()) { // only add objects that we actually have ObjectId oid = r.getObjectId(); - if (local.hasObject(oid)) + if (local.getObjectDatabase().has(oid)) remoteObjects.add(oid); } remoteObjects.addAll(additionalHaves); 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 03763368a8..6f17620d91 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -80,6 +80,7 @@ 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.internal.transport.parser.FirstCommand; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.Config; @@ -119,10 +120,14 @@ import org.eclipse.jgit.util.io.TimeoutOutputStream; * Subclasses compose these operations into full service implementations. */ public abstract class BaseReceivePack { - /** Data in the first line of a request, the line itself plus capabilities. */ + /** + * Data in the first line of a request, the line itself plus capabilities. + * + * @deprecated Use {@link FirstCommand} instead. + */ + @Deprecated public static class FirstLine { - private final String line; - private final Set<String> capabilities; + private final FirstCommand command; /** * Parse the first line of a receive-pack request. @@ -131,33 +136,25 @@ public abstract class BaseReceivePack { * line from the client. */ public FirstLine(String line) { - final HashSet<String> caps = new HashSet<>(); - final int nul = line.indexOf('\0'); - if (nul >= 0) { - for (String c : line.substring(nul + 1).split(" ")) //$NON-NLS-1$ - caps.add(c); - this.line = line.substring(0, nul); - } else - this.line = line; - this.capabilities = Collections.unmodifiableSet(caps); + command = FirstCommand.fromLine(line); } /** @return non-capabilities part of the line. */ public String getLine() { - return line; + return command.getLine(); } /** @return capabilities parsed from the line. */ public Set<String> getCapabilities() { - return capabilities; + return command.getCapabilities(); } } /** Database we write the stored objects into. */ - private final Repository db; + final Repository db; /** Revision traversal support over {@link #db}. */ - private final RevWalk walk; + final RevWalk walk; /** * Is the client connection a bi-directional socket or pipe? @@ -207,7 +204,7 @@ public abstract class BaseReceivePack { private AdvertiseRefsHook advertiseRefsHook; /** Filter used while advertising the refs to the client. */ - private RefFilter refFilter; + RefFilter refFilter; /** Timeout in seconds to wait for client interaction. */ private int timeout; @@ -242,10 +239,10 @@ public abstract class BaseReceivePack { private PackParser parser; /** The refs we advertised as existing at the start of the connection. */ - private Map<String, Ref> refs; + Map<String, Ref> refs; /** All SHA-1s shown to the client, which can be possible edges. */ - private Set<ObjectId> advertisedHaves; + Set<ObjectId> advertisedHaves; /** Capabilities requested by the client. */ private Set<String> enabledCapabilities; @@ -278,7 +275,7 @@ public abstract class BaseReceivePack { private PushCertificateParser pushCertificateParser; private SignedPushConfig signedPushConfig; - private PushCertificate pushCert; + PushCertificate pushCert; private ReceivedPackStatistics stats; /** @@ -289,10 +286,10 @@ public abstract class BaseReceivePack { * @return the parsed certificate, or null if push certificates are disabled * or no cert was presented by the client. * @since 4.1 + * @deprecated use {@link ReceivePack#getPushCertificate}. */ - public PushCertificate getPushCertificate() { - return pushCert; - } + @Deprecated + public abstract PushCertificate getPushCertificate(); /** * Set the push certificate used to verify the pusher's identity. @@ -303,10 +300,10 @@ public abstract class BaseReceivePack { * @param cert * the push certificate to set. * @since 4.1 + * @deprecated use {@link ReceivePack#setPushCertificate(PushCertificate)}. */ - public void setPushCertificate(PushCertificate cert) { - pushCert = cert; - } + @Deprecated + public abstract void setPushCertificate(PushCertificate cert); /** * Create a new pack receive for an open repository. @@ -424,29 +421,29 @@ public abstract class BaseReceivePack { * Get the repository this receive completes into. * * @return the repository this receive completes into. + * @deprecated use {@link ReceivePack#getRepository} */ - public final Repository getRepository() { - return db; - } + @Deprecated + public abstract Repository getRepository(); /** * Get the RevWalk instance used by this connection. * * @return the RevWalk instance used by this connection. + * @deprecated use {@link ReceivePack#getRevWalk} */ - public final RevWalk getRevWalk() { - return walk; - } + @Deprecated + public abstract RevWalk getRevWalk(); /** * Get refs which were advertised to the client. * * @return all refs which were advertised to the client, or null if * {@link #setAdvertisedRefs(Map, Set)} has not been called yet. + * @deprecated use {@link ReceivePack#getAdvertisedRefs} */ - public final Map<String, Ref> getAdvertisedRefs() { - return refs; - } + @Deprecated + public abstract Map<String, Ref> getAdvertisedRefs(); /** * Set the refs advertised by this ReceivePack. @@ -464,25 +461,10 @@ public abstract class BaseReceivePack { * explicit set of additional haves to claim as advertised. If * null, assumes the default set of additional haves from the * repository. + * @deprecated use {@link ReceivePack#setAdvertisedRefs} */ - public void setAdvertisedRefs(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves) { - refs = allRefs != null ? allRefs : db.getAllRefs(); - refs = refFilter.filter(refs); - advertisedHaves.clear(); - - Ref head = refs.get(Constants.HEAD); - if (head != null && head.isSymbolic()) - refs.remove(Constants.HEAD); - - for (Ref ref : refs.values()) { - if (ref.getObjectId() != null) - advertisedHaves.add(ref.getObjectId()); - } - if (additionalHaves != null) - advertisedHaves.addAll(additionalHaves); - else - advertisedHaves.addAll(db.getAdditionalHaves()); - } + @Deprecated + public abstract void setAdvertisedRefs(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves); /** * Get objects advertised to the client. @@ -1310,7 +1292,7 @@ public abstract class BaseReceivePack { if (firstPkt) { firstPkt = false; - FirstLine firstLine = new FirstLine(line); + FirstCommand firstLine = FirstCommand.fromLine(line); enabledCapabilities = firstLine.getCapabilities(); line = firstLine.getLine(); enableCapabilities(); @@ -1606,7 +1588,7 @@ public abstract class BaseReceivePack { throw new MissingObjectException(o, o.getType()); } - if (o instanceof RevBlob && !db.hasObject(o)) + if (o instanceof RevBlob && !db.getObjectDatabase().has(o)) throw new MissingObjectException(o, Constants.TYPE_BLOB); } checking.endTask(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java index 4b20f6c8b0..84a0972723 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java @@ -50,6 +50,7 @@ package org.eclipse.jgit.transport; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.BufferedInputStream; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; @@ -165,9 +166,13 @@ class BundleFetchConnection extends BaseFetchConnection { while (!done) { bin.mark(hdrbuf.length); final int cnt = bin.read(hdrbuf); + if (cnt < 0) { + throw new EOFException(JGitText.get().shortReadOfBlock); + } int lf = 0; - while (lf < cnt && hdrbuf[lf] != '\n') + while (lf < cnt && hdrbuf[lf] != '\n') { lf++; + } bin.reset(); IO.skipFully(bin, lf); if (lf < cnt && hdrbuf[lf] == '\n') { 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 211707e9ad..681ae125cb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -181,7 +181,7 @@ class FetchProcess { ObjectId id = r.getPeeledObjectId(); if (id == null) id = r.getObjectId(); - if (transport.local.hasObject(id)) + if (localHasObject(id)) wantTag(r); } @@ -393,6 +393,18 @@ class FetchProcess { } } + private boolean localHasObject(ObjectId id) throws TransportException { + try { + return transport.local.getObjectDatabase().has(id); + } catch (IOException err) { + throw new TransportException( + MessageFormat.format( + JGitText.get().readingObjectsFromLocalRepositoryFailed, + err.getMessage()), + err); + } + } + private Collection<Ref> expandAutoFollowTags() throws TransportException { final Collection<Ref> additionalTags = new ArrayList<>(); final Map<String, Ref> haveRefs = localRefs(); @@ -410,7 +422,7 @@ class FetchProcess { if (obj == null) obj = r.getObjectId(); - if (askFor.containsKey(obj) || transport.local.hasObject(obj)) + if (askFor.containsKey(obj) || localHasObject(obj)) wantTag(r); else additionalTags.add(r); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java index fe7aaf7699..075cc9c113 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java @@ -44,7 +44,7 @@ package org.eclipse.jgit.transport; /** - * Internal API to to assist {@code org.eclipse.jgit.http.server}. + * Internal API to assist {@code org.eclipse.jgit.http.server}. * <p> * <b>Do not call.</b> * 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 fc22034340..c573d1248f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java @@ -81,7 +81,7 @@ public interface NonceGenerator { * such that the pusher cannot forge nonces by pushing to another * repository at the same time as well and reusing the nonce. * @param allowSlop - * If the receiving backend is is able to generate slop. This is + * If the receiving backend is able to generate slop. This is * the case for serving via http protocol using more than one * http frontend. The client would talk to different http * frontends, which may have a slight difference of time due to diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java index 49acb4d9d8..2b2795fefb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -1237,7 +1237,7 @@ public abstract class PackParser { bAvail -= cnt; } - // Ensure at least need bytes are available in in {@link #buf}. + // Ensure at least need bytes are available in {@link #buf}. int fill(Source src, int need) throws IOException { while (bAvail < need) { int next = bOffset + bAvail; @@ -1568,7 +1568,7 @@ public abstract class PackParser { long inflatedSize) throws IOException; /** - * Event notifying the the current object. + * Event notifying the current object. * *@param info * object information. @@ -1616,7 +1616,7 @@ public abstract class PackParser { AnyObjectId baseId, long inflatedSize) throws IOException; /** - * Event notifying the the current object. + * Event notifying the current object. * *@return object information that must be populated with at least the * offset. 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 577aaf4e9e..4652c3fda8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; @@ -53,13 +54,18 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.UnpackException; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand.Result; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; @@ -93,6 +99,106 @@ public class ReceivePack extends BaseReceivePack { } /** + * Get the repository this receive completes into. + * + * @return the repository this receive completes into. + */ + @Override + public final Repository getRepository() { + return db; + } + + /** + * Get the RevWalk instance used by this connection. + * + * @return the RevWalk instance used by this connection. + */ + @Override + public final RevWalk getRevWalk() { + return walk; + } + + /** + * Get refs which were advertised to the client. + * + * @return all refs which were advertised to the client, or null if + * {@link #setAdvertisedRefs(Map, Set)} has not been called yet. + */ + @Override + public final Map<String, Ref> getAdvertisedRefs() { + return refs; + } + + /** + * Set the refs advertised by this ReceivePack. + * <p> + * Intended to be called from a + * {@link org.eclipse.jgit.transport.PreReceiveHook}. + * + * @param allRefs + * explicit set of references to claim as advertised by this + * ReceivePack instance. This overrides any references that may + * exist in the source repository. The map is passed to the + * configured {@link #getRefFilter()}. If null, assumes all refs + * were advertised. + * @param additionalHaves + * explicit set of additional haves to claim as advertised. If + * null, assumes the default set of additional haves from the + * repository. + */ + @Override + public void setAdvertisedRefs(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves) { + refs = allRefs != null ? allRefs : db.getAllRefs(); + refs = refFilter.filter(refs); + advertisedHaves.clear(); + + Ref head = refs.get(HEAD); + if (head != null && head.isSymbolic()) { + refs.remove(HEAD); + } + + for (Ref ref : refs.values()) { + if (ref.getObjectId() != null) { + advertisedHaves.add(ref.getObjectId()); + } + } + if (additionalHaves != null) { + advertisedHaves.addAll(additionalHaves); + } else { + advertisedHaves.addAll(db.getAdditionalHaves()); + } + } + + /** + * Get the push certificate used to verify the pusher's identity. + * <p> + * Only valid after commands are read from the wire. + * + * @return the parsed certificate, or null if push certificates are disabled + * or no cert was presented by the client. + * @since 4.1 + */ + @Override + public PushCertificate getPushCertificate() { + return pushCert; + } + + /** + * Set the push certificate used to verify the pusher's identity. + * <p> + * Should only be called if reconstructing an instance without going through + * the normal {@link #recvCommands()} flow. + * + * @param cert + * the push certificate to set. + * @since 4.1 + */ + @Override + public void setPushCertificate(PushCertificate cert) { + pushCert = cert; + } + + /** * Gets an unmodifiable view of the option strings associated with the push. * * @return an unmodifiable view of pushOptions, or null (if pushOptions is). 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 9a67f0f8fe..c34e3b8e61 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java @@ -293,7 +293,7 @@ public class RemoteRefUpdate { final boolean forceUpdate, final String localName, final ObjectId expectedOldObjectId) throws IOException { if (remoteName == null) - throw new IllegalArgumentException(JGitText.get().remoteNameCantBeNull); + throw new IllegalArgumentException(JGitText.get().remoteNameCannotBeNull); if (srcId == null && srcRef != null) throw new IOException(MessageFormat.format( JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef)); 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 2fbcaa2928..1d0f836619 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.transport; +import static java.util.Collections.unmodifiableMap; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; import static org.eclipse.jgit.lib.Constants.R_TAGS; @@ -79,9 +80,11 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -421,7 +424,7 @@ public class UploadPack { * configured {@link #getRefFilter()}. If null, assumes all refs * were advertised. */ - public void setAdvertisedRefs(Map<String, Ref> allRefs) { + public void setAdvertisedRefs(@Nullable Map<String, Ref> allRefs) { if (allRefs != null) refs = allRefs; else @@ -545,7 +548,7 @@ public class UploadPack { * custom validator for client want list. * @since 3.1 */ - public void setRequestValidator(RequestValidator validator) { + public void setRequestValidator(@Nullable RequestValidator validator) { requestValidator = validator != null ? validator : new AdvertisedRequestValidator(); } @@ -579,21 +582,21 @@ public class UploadPack { * @param advertiseRefsHook * the hook; may be null to show all refs. */ - public void setAdvertiseRefsHook(AdvertiseRefsHook advertiseRefsHook) { - if (advertiseRefsHook != null) - this.advertiseRefsHook = advertiseRefsHook; - else - this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + public void setAdvertiseRefsHook( + @Nullable AdvertiseRefsHook advertiseRefsHook) { + this.advertiseRefsHook = advertiseRefsHook != null ? advertiseRefsHook + : AdvertiseRefsHook.DEFAULT; } /** * Set the protocol V2 hook. * * @param hook + * the hook; if null no special actions are taken. * @since 5.1 */ - public void setProtocolV2Hook(ProtocolV2Hook hook) { - this.protocolV2Hook = hook; + public void setProtocolV2Hook(@Nullable ProtocolV2Hook hook) { + this.protocolV2Hook = hook != null ? hook : ProtocolV2Hook.DEFAULT; } /** @@ -608,7 +611,7 @@ public class UploadPack { * @param refFilter * the filter; may be null to show all refs. */ - public void setRefFilter(RefFilter refFilter) { + public void setRefFilter(@Nullable RefFilter refFilter) { this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; } @@ -627,7 +630,7 @@ public class UploadPack { * @param hook * the hook; if null no special actions are taken. */ - public void setPreUploadHook(PreUploadHook hook) { + public void setPreUploadHook(@Nullable PreUploadHook hook) { preUploadHook = hook != null ? hook : PreUploadHook.NULL; } @@ -648,7 +651,7 @@ public class UploadPack { * the hook; if null no special actions are taken. * @since 4.1 */ - public void setPostUploadHook(PostUploadHook hook) { + public void setPostUploadHook(@Nullable PostUploadHook hook) { postUploadHook = hook != null ? hook : PostUploadHook.NULL; } @@ -659,7 +662,7 @@ public class UploadPack { * configuration controlling packing parameters. If null the * source repository's settings will be used. */ - public void setPackConfig(PackConfig pc) { + public void setPackConfig(@Nullable PackConfig pc) { this.packConfig = pc; } @@ -671,7 +674,7 @@ public class UploadPack { * repository's settings will be used. * @since 3.1 */ - public void setTransferConfig(TransferConfig tc) { + public void setTransferConfig(@Nullable TransferConfig tc) { this.transferConfig = tc != null ? tc : new TransferConfig(db); if (transferConfig.isAllowTipSha1InWant()) { setRequestPolicy(transferConfig.isAllowReachableSha1InWant() @@ -849,22 +852,46 @@ public class UploadPack { } /** - * Read a ref on behalf of the client. + * Returns the specified references. * <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. + * This produces an immutable map containing whatever subset of the + * refs named by the caller are present in the supplied {@code refs} + * map. * - * @param name - * the unabbreviated name of the reference. - * @return the requested Ref, or {@code null} if it is not visible or - * does not exist. + * @param refs + * Map to search for refs to return. + * @param names + * which refs to search for in {@code refs}. + * @return the requested Refs, omitting any that are null or missing. + */ + @NonNull + private static Map<String, Ref> mapRefs( + Map<String, Ref> refs, List<String> names) { + return unmodifiableMap( + names.stream() + .map(refs::get) + .filter(Objects::nonNull) + .collect(toMap(Ref::getName, identity(), (a, b) -> b))); + } + + /** + * Read refs on behalf of the client. + * <p> + * This checks whether the refs are present in the ref advertisement + * since otherwise the client might not be supposed to be able to + * read them. + * + * @param names + * unabbreviated names of references. + * @return the requested Refs, omitting any that are not visible or + * do not exist. * @throws java.io.IOException - * on failure to read the ref or check it for visibility. + * on failure to read a ref or check it for visibility. */ - @Nullable - private Ref getRef(String name) throws IOException { + @NonNull + private Map<String, Ref> exactRefs(List<String> names) throws IOException { if (refs != null) { - return refs.get(name); + return mapRefs(refs, names); } if (!advertiseRefsHookCalled) { advertiseRefsHook.advertiseRefs(this); @@ -874,9 +901,10 @@ public class UploadPack { refFilter == RefFilter.DEFAULT && transferConfig.hasDefaultRefFilter()) { // Fast path: no ref filtering is needed. - return db.getRefDatabase().exactRef(name); + String[] ns = names.toArray(new String[0]); + return unmodifiableMap(db.getRefDatabase().exactRef(ns)); } - return getAdvertisedOrDefaultRefs().get(name); + return mapRefs(getAdvertisedOrDefaultRefs(), names); } /** @@ -906,7 +934,7 @@ public class UploadPack { refFilter == RefFilter.DEFAULT && transferConfig.hasDefaultRefFilter()) { // Fast path: no ref filtering is needed. - return db.getRefDatabase().getRef(name); + return db.getRefDatabase().findRef(name); } return RefDatabase.findRef(getAdvertisedOrDefaultRefs(), name); } @@ -1042,6 +1070,31 @@ public class UploadPack { adv.end(); } + // Resolves ref names from the request's want-ref lines to + // object ids, throwing PackProtocolException if any are missing. + private Map<String, ObjectId> wantedRefs(FetchV2Request req) + throws IOException { + Map<String, ObjectId> result = new TreeMap<>(); + + List<String> wanted = req.getWantedRefs(); + Map<String, Ref> resolved = exactRefs(wanted); + + for (String refName : wanted) { + Ref ref = resolved.get(refName); + if (ref == null) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidRefName, refName)); + } + ObjectId oid = ref.getObjectId(); + if (oid == null) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidRefName, refName)); + } + result.put(refName, oid); + } + return result; + } + private void fetchV2() throws IOException { // Depending on the requestValidator, #processHaveLines may // require that advertised be set. Set it only in the required @@ -1074,22 +1127,9 @@ public class UploadPack { deepenNots.add(ref.getObjectId()); } - Map<String, ObjectId> wantedRefs = new TreeMap<>(); - for (String refName : req.getWantedRefs()) { - Ref ref = getRef(refName); - if (ref == null) { - throw new PackProtocolException(MessageFormat - .format(JGitText.get().invalidRefName, refName)); - } - ObjectId oid = ref.getObjectId(); - if (oid == null) { - throw new PackProtocolException(MessageFormat - .format(JGitText.get().invalidRefName, refName)); - } - // TODO(ifrade): Avoid mutating the parsed request. - req.getWantIds().add(oid); - wantedRefs.put(refName, oid); - } + Map<String, ObjectId> wantedRefs = wantedRefs(req); + // TODO(ifrade): Avoid mutating the parsed request. + req.getWantIds().addAll(wantedRefs.values()); wantIds = req.getWantIds(); boolean sectionSent = false; @@ -1486,6 +1526,20 @@ public class UploadPack { } /** + * Returns the filter blob limit for the current request. Valid only after + * calling recvWants(). A limit -1 means no limit. + * + * @return filter blob limit requested by the client, or -1 if no limit + * @since 5.3 + */ + public long getFilterBlobLimit() { + if (currentRequest == null) { + throw new RequestNotYetReadException(); + } + return currentRequest.getFilterBlobLimit(); + } + + /** * Get the user agent of the client. * <p> * If the client is new enough to use {@code agent=} capability that value @@ -2080,6 +2134,7 @@ public class UploadPack { : req.getDepth() - 1; pw.setShallowPack(req.getDepth(), unshallowCommits); + @SuppressWarnings("resource") // Ownership is transferred below DepthWalk.RevWalk dw = new DepthWalk.RevWalk( walk.getObjectReader(), walkDepth); dw.setDeepenSince(req.getDeepenSince()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java index 9307914444..2bb58144ba 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -555,10 +555,10 @@ class WalkFetchConnection extends BaseFetchConnection { continue; } finally { // If the pack was good its in the local repository - // and Repository.hasObject(id) will succeed in the - // future, so we do not need this data anymore. If - // it failed the index and pack are unusable and we - // shouldn't consult them again. + // and Repository.getObjectDatabase().has(id) will + // succeed in the future, so we do not need this + // data any more. If it failed the index and pack + // are unusable and we shouldn't consult them again. // try { if (pack.tmpIdx != null) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java index 6d4df4fbad..5c67253cfc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java @@ -348,7 +348,7 @@ abstract class WalkRemoteObjectDatabase { /** * Open a buffered reader around a file. * <p> - * This method is suitable for for reading line-oriented resources like + * This method is suitable for reading line-oriented resources like * <code>info/packs</code>, <code>info/refs</code>, and the alternates list. * * @return a stream to read from the file. Never null. 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 ddf916f41f..90524fedaf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -75,6 +75,7 @@ import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.ignore.FastIgnoreRule; @@ -1498,9 +1499,18 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { private EolStreamType getEolStreamType(OperationType opType) throws IOException { if (eolStreamTypeHolder == null) { - EolStreamType type=null; + EolStreamType type = null; if (state.walk != null) { type = state.walk.getEolStreamType(opType); + OperationType operationType = opType != null ? opType + : state.walk.getOperationType(); + if (OperationType.CHECKIN_OP.equals(operationType) + && EolStreamType.AUTO_LF.equals(type) + && hasCrLfInIndex(getDirCacheIterator())) { + // If text=auto (or core.autocrlf=true) and the file has + // already been committed with CR/LF, then don't convert. + type = EolStreamType.DIRECT; + } } else { switch (getOptions().getAutoCRLF()) { case FALSE: @@ -1517,6 +1527,59 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { return eolStreamTypeHolder.get(); } + /** + * Determines whether the file was committed un-normalized. If the iterator + * points to a conflict entry, checks the "ours" version. + * + * @param dirCache + * iterator pointing to the current entry for the file in the + * index + * @return {@code true} if the file in the index is not binary and has CR/LF + * line endings, {@code false} otherwise + */ + private boolean hasCrLfInIndex(DirCacheIterator dirCache) { + if (dirCache == null) { + return false; + } + // Read blob from index and check for CR/LF-delimited text. + DirCacheEntry entry = dirCache.getDirCacheEntry(); + if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) { + ObjectId blobId = entry.getObjectId(); + if (entry.getStage() > 0 + && entry.getStage() != DirCacheEntry.STAGE_2) { + // Merge conflict: check ours (stage 2) + byte[] name = entry.getRawPath(); + int i = 0; + while (!dirCache.eof()) { + dirCache.next(1); + i++; + entry = dirCache.getDirCacheEntry(); + if (!Arrays.equals(name, entry.getRawPath())) { + break; + } + if (entry.getStage() == DirCacheEntry.STAGE_2) { + blobId = entry.getObjectId(); + break; + } + } + dirCache.back(i); + } + try (ObjectReader reader = repository.newObjectReader()) { + ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB); + try { + return RawText.isCrLfText(loader.getCachedBytes()); + } catch (LargeObjectException e) { + try (InputStream in = loader.openStream()) { + return RawText.isCrLfText(in); + } + } + } catch (IOException e) { + // Ignore and return false below + } + } + return false; + } + private boolean isDirectoryIgnored(String pathRel) throws IOException { final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset; final String base = TreeWalk.pathOf(this.path, 0, pOff); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java index eda8afb10f..eda0fae247 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java @@ -51,6 +51,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.Charset; +import java.nio.file.AccessDeniedException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.InvalidPathException; @@ -386,8 +387,9 @@ public class FS_POSIX extends FS { Integer nlink = (Integer) (Files.getAttribute(lockPath, "unix:nlink")); //$NON-NLS-1$ if (nlink > 2) { - LOG.warn("nlink of link to lock file {} was not 2 but {}", //$NON-NLS-1$ - lock.getPath(), nlink); + LOG.warn(MessageFormat.format( + JGitText.get().failedAtomicFileCreation, lockPath, + nlink)); return false; } else if (nlink < 2) { supportsUnixNLink = false; @@ -453,7 +455,8 @@ public class FS_POSIX extends FS { supportsUnixNLink = false; } return token(true, link); - } catch (UnsupportedOperationException | IllegalArgumentException e) { + } catch (UnsupportedOperationException | IllegalArgumentException + | AccessDeniedException | SecurityException e) { supportsUnixNLink = false; return token(true, link); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java index e88e7a38c6..a07a4fd1a5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java @@ -203,12 +203,13 @@ public class IO { if (last < 0) return ByteBuffer.wrap(out, 0, pos); - @SuppressWarnings("resource" /* java 7 */) - TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(Integer.MAX_VALUE); - tmp.write(out); - tmp.write(last); - tmp.copy(in); - return ByteBuffer.wrap(tmp.toByteArray()); + try (TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap( + Integer.MAX_VALUE)) { + tmp.write(out); + tmp.write(last); + tmp.copy(in); + return ByteBuffer.wrap(tmp.toByteArray()); + } } /** 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 a440cb275c..2081bace2a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java @@ -75,7 +75,9 @@ public final class RawParseUtils { * UTF-8 charset constant. * * @since 2.2 + * @deprecated use {@link java.nio.charset.StandardCharsets#UTF_8} instead */ + @Deprecated public static final Charset UTF8_CHARSET = UTF_8; private static final byte[] digits10; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java index 08377e6be0..4c60862bf4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java @@ -144,9 +144,18 @@ public class AutoCRLFInputStream extends InputStream { } private boolean fillBuffer() throws IOException { - cnt = in.read(buf, 0, buf.length); - if (cnt < 1) + cnt = 0; + while (cnt < buf.length) { + int n = in.read(buf, cnt, buf.length - cnt); + if (n < 0) { + break; + } + cnt += n; + } + if (cnt < 1) { + cnt = -1; return false; + } if (detectBinary) { isBinary = RawText.isBinary(buf, cnt); detectBinary = false; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java index ff28161a52..280cf7e28c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java @@ -189,9 +189,18 @@ public class AutoLFInputStream extends InputStream { } private boolean fillBuffer() throws IOException { - cnt = in.read(buf, 0, buf.length); - if (cnt < 1) + cnt = 0; + while (cnt < buf.length) { + int n = in.read(buf, cnt, buf.length - cnt); + if (n < 0) { + break; + } + cnt += n; + } + if (cnt < 1) { + cnt = -1; return false; + } if (detectBinary) { isBinary = RawText.isBinary(buf, cnt); detectBinary = false; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java index 9fe01f1d8d..1ad6602fce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java @@ -48,8 +48,10 @@ import static java.lang.Integer.numberOfTrailingZeros; import static java.lang.Integer.rotateLeft; import static java.lang.Integer.rotateRight; +import java.text.MessageFormat; import java.util.Arrays; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.util.NB; @@ -497,7 +499,8 @@ public class SHA1 { if (foundCollision) { ObjectId id = h.toObjectId(); - LOG.warn("possible SHA-1 collision " + id.name()); //$NON-NLS-1$ + LOG.warn(MessageFormat.format(JGitText.get().sha1CollisionDetected, + id.name())); throw new Sha1CollisionException(id); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java index dcefe95498..0e5c9192d1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/Sha1CollisionException.java @@ -65,7 +65,7 @@ public class Sha1CollisionException extends RuntimeException { */ public Sha1CollisionException(ObjectId id) { super(MessageFormat.format( - JGitText.get().sha1CollisionDetected1, + JGitText.get().sha1CollisionDetected, id.name())); } } |