diff options
Diffstat (limited to 'org.eclipse.jgit')
35 files changed, 1224 insertions, 469 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters deleted file mode 100644 index e2565bd6b3..0000000000 --- a/org.eclipse.jgit/.settings/.api_filters +++ /dev/null @@ -1,73 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<component id="org.eclipse.jgit" version="2"> - <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.transport.CredentialsProviderUserInfo"> - <filter id="305324134"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.CredentialsProviderUserInfo"/> - <message_argument value="org.eclipse.jgit_5.8.0"/> - </message_arguments> - </filter> - </resource> - <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.transport.DefaultSshSessionFactory"> - <filter id="305324134"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.DefaultSshSessionFactory"/> - <message_argument value="org.eclipse.jgit_5.8.0"/> - </message_arguments> - </filter> - </resource> - <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.transport.JschConfigSessionFactory"> - <filter id="305324134"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.JschConfigSessionFactory"/> - <message_argument value="org.eclipse.jgit_5.8.0"/> - </message_arguments> - </filter> - </resource> - <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.transport.JschSession"> - <filter id="305324134"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.JschSession"/> - <message_argument value="org.eclipse.jgit_5.8.0"/> - </message_arguments> - </filter> - </resource> - <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.transport.OpenSshConfig"> - <filter id="305324134"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.OpenSshConfig"/> - <message_argument value="org.eclipse.jgit_5.8.0"/> - </message_arguments> - </filter> - </resource> - <resource path="src/org/eclipse/jgit/lib/BitmapIndex.java" type="org.eclipse.jgit.lib.BitmapIndex$Bitmap"> - <filter id="403804204"> - <message_arguments> - <message_argument value="org.eclipse.jgit.lib.BitmapIndex.Bitmap"/> - <message_argument value="retrieveCompressed()"/> - </message_arguments> - </filter> - </resource> - <resource path="src/org/eclipse/jgit/transport/SshSessionFactory.java" type="org.eclipse.jgit.transport.SshSessionFactory"> - <filter id="336695337"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.SshSessionFactory"/> - <message_argument value="getType()"/> - </message_arguments> - </filter> - </resource> - <resource path="src/org/eclipse/jgit/transport/http/HttpConnection.java" type="org.eclipse.jgit.transport.http.HttpConnection"> - <filter id="403767336"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.http.HttpConnection"/> - <message_argument value="HTTP_11_MOVED_PERM"/> - </message_arguments> - </filter> - <filter id="403767336"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.http.HttpConnection"/> - <message_argument value="HTTP_NOT_AUTHORITATIVE"/> - </message_arguments> - </filter> - </resource> -</component> diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD index f7970976b0..2083372248 100644 --- a/org.eclipse.jgit/BUILD +++ b/org.eclipse.jgit/BUILD @@ -14,7 +14,7 @@ SRCS = glob( RESOURCES = glob(["resources/**"]) java_library( - name = "jgit", + name = "jgit_non_stamped", srcs = SRCS, resource_strip_prefix = "org.eclipse.jgit/resources", resources = RESOURCES, @@ -25,6 +25,27 @@ java_library( ], ) +genrule( + name = "jgit", + srcs = [":jgit_non_stamped"], + outs = ["jgit.jar"], + cmd = " && ".join([ + "ROOT=$$PWD", + "TMP=$$(mktemp -d || mktemp -d -t bazel-tmp)", + "TZ=UTC", + "export TZ", + "GEN_VERSION=$$(cat bazel-out/stable-status.txt | grep -w STABLE_BUILD_JGIT_LABEL | cut -d ' ' -f 2)", + "cd $$TMP", + "unzip -q $$ROOT/$<", + "echo \"Implementation-Version: $$GEN_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF", + "find . -exec touch '{}' ';'", + "zip -Xqr $$ROOT/$@ .", + "rm -rf $$TMP", + ]), + stamp = 1, + visibility = ["//visibility:public"], +) + java_library( name = "insecure_cipher_factory", srcs = INSECURE_CIPHER_FACTORY, diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index e83d320811..1348bde29e 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -3,12 +3,12 @@ Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Automatic-Module-Name: org.eclipse.jgit Bundle-SymbolicName: org.eclipse.jgit -Bundle-Version: 5.8.2.qualifier +Bundle-Version: 5.9.1.qualifier Bundle-Localization: plugin Bundle-Vendor: %Bundle-Vendor Eclipse-ExtensibleAPI: true -Export-Package: org.eclipse.jgit.annotations;version="5.8.2", - org.eclipse.jgit.api;version="5.8.2"; +Export-Package: org.eclipse.jgit.annotations;version="5.9.1", + org.eclipse.jgit.api;version="5.9.1"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.notes, org.eclipse.jgit.dircache, @@ -23,18 +23,18 @@ Export-Package: org.eclipse.jgit.annotations;version="5.8.2", org.eclipse.jgit.revwalk.filter, org.eclipse.jgit.blame, org.eclipse.jgit.merge", - org.eclipse.jgit.api.errors;version="5.8.2"; + org.eclipse.jgit.api.errors;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.errors", - org.eclipse.jgit.attributes;version="5.8.2"; + org.eclipse.jgit.attributes;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk", - org.eclipse.jgit.blame;version="5.8.2"; + org.eclipse.jgit.blame;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff", - org.eclipse.jgit.diff;version="5.8.2"; + org.eclipse.jgit.diff;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.attributes, org.eclipse.jgit.revwalk, @@ -42,47 +42,47 @@ Export-Package: org.eclipse.jgit.annotations;version="5.8.2", org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.treewalk, org.eclipse.jgit.util", - org.eclipse.jgit.dircache;version="5.8.2"; + org.eclipse.jgit.dircache;version="5.9.1"; uses:="org.eclipse.jgit.events, org.eclipse.jgit.lib, org.eclipse.jgit.attributes, org.eclipse.jgit.treewalk, org.eclipse.jgit.util", - org.eclipse.jgit.errors;version="5.8.2"; + org.eclipse.jgit.errors;version="5.9.1"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.dircache, org.eclipse.jgit.lib, org.eclipse.jgit.internal.storage.pack", - org.eclipse.jgit.events;version="5.8.2"; + org.eclipse.jgit.events;version="5.9.1"; uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.fnmatch;version="5.8.2", - org.eclipse.jgit.gitrepo;version="5.8.2"; + org.eclipse.jgit.fnmatch;version="5.9.1", + org.eclipse.jgit.gitrepo;version="5.9.1"; uses:="org.xml.sax.helpers, org.eclipse.jgit.api, org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.xml.sax", - org.eclipse.jgit.gitrepo.internal;version="5.8.2";x-internal:=true, - org.eclipse.jgit.hooks;version="5.8.2";uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.ignore;version="5.8.2", - org.eclipse.jgit.ignore.internal;version="5.8.2"; + org.eclipse.jgit.gitrepo.internal;version="5.9.1";x-internal:=true, + org.eclipse.jgit.hooks;version="5.9.1";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.ignore;version="5.9.1", + org.eclipse.jgit.ignore.internal;version="5.9.1"; x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.internal;version="5.8.2"; + org.eclipse.jgit.internal;version="5.9.1"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.http.test", - org.eclipse.jgit.internal.fsck;version="5.8.2"; + org.eclipse.jgit.internal.fsck;version="5.9.1"; x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.internal.ketch;version="5.8.2"; + org.eclipse.jgit.internal.ketch;version="5.9.1"; x-friends:="org.eclipse.jgit.junit, org.eclipse.jgit.test, org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.revwalk;version="5.8.2";x-internal:=true, - org.eclipse.jgit.internal.storage.dfs;version="5.8.2"; + org.eclipse.jgit.internal.revwalk;version="5.9.1";x-internal:=true, + org.eclipse.jgit.internal.storage.dfs;version="5.9.1"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.http.server, org.eclipse.jgit.http.test, org.eclipse.jgit.lfs.test", - org.eclipse.jgit.internal.storage.file;version="5.8.2"; + org.eclipse.jgit.internal.storage.file;version="5.9.1"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.junit, org.eclipse.jgit.junit.http, @@ -91,35 +91,35 @@ Export-Package: org.eclipse.jgit.annotations;version="5.8.2", org.eclipse.jgit.pgm, org.eclipse.jgit.pgm.test, org.eclipse.jgit.ssh.apache", - org.eclipse.jgit.internal.storage.io;version="5.8.2"; + org.eclipse.jgit.internal.storage.io;version="5.9.1"; x-friends:="org.eclipse.jgit.junit, org.eclipse.jgit.test, org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.storage.pack;version="5.8.2"; + org.eclipse.jgit.internal.storage.pack;version="5.9.1"; x-friends:="org.eclipse.jgit.junit, org.eclipse.jgit.test, org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.storage.reftable;version="5.8.2"; + org.eclipse.jgit.internal.storage.reftable;version="5.9.1"; 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.8.2"; + org.eclipse.jgit.internal.storage.reftree;version="5.9.1"; x-friends:="org.eclipse.jgit.junit, org.eclipse.jgit.test, org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.submodule;version="5.8.2";x-internal:=true, - org.eclipse.jgit.internal.transport.connectivity;version="5.8.2"; + org.eclipse.jgit.internal.submodule;version="5.9.1";x-internal:=true, + org.eclipse.jgit.internal.transport.connectivity;version="5.9.1"; x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.internal.transport.http;version="5.8.2"; + org.eclipse.jgit.internal.transport.http;version="5.9.1"; x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.internal.transport.parser;version="5.8.2"; + org.eclipse.jgit.internal.transport.parser;version="5.9.1"; x-friends:="org.eclipse.jgit.http.server, org.eclipse.jgit.test", - org.eclipse.jgit.internal.transport.ssh;version="5.8.2"; + org.eclipse.jgit.internal.transport.ssh;version="5.9.1"; x-friends:="org.eclipse.jgit.ssh.apache, org.eclipse.jgit.ssh.jsch", - org.eclipse.jgit.lib;version="5.8.2"; + org.eclipse.jgit.lib;version="5.9.1"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.util.sha1, org.eclipse.jgit.dircache, @@ -133,9 +133,9 @@ Export-Package: org.eclipse.jgit.annotations;version="5.8.2", org.eclipse.jgit.util, org.eclipse.jgit.submodule, org.eclipse.jgit.util.time", - org.eclipse.jgit.lib.internal;version="5.8.2"; + org.eclipse.jgit.lib.internal;version="5.9.1"; x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.merge;version="5.8.2"; + org.eclipse.jgit.merge;version="5.9.1"; uses:="org.eclipse.jgit.dircache, org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, @@ -144,40 +144,40 @@ Export-Package: org.eclipse.jgit.annotations;version="5.8.2", org.eclipse.jgit.util, org.eclipse.jgit.api, org.eclipse.jgit.attributes", - org.eclipse.jgit.nls;version="5.8.2", - org.eclipse.jgit.notes;version="5.8.2"; + org.eclipse.jgit.nls;version="5.9.1", + org.eclipse.jgit.notes;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk, org.eclipse.jgit.merge", - org.eclipse.jgit.patch;version="5.8.2"; + org.eclipse.jgit.patch;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.diff", - org.eclipse.jgit.revplot;version="5.8.2"; + org.eclipse.jgit.revplot;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk", - org.eclipse.jgit.revwalk;version="5.8.2"; + org.eclipse.jgit.revwalk;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.diff, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.revwalk.filter, org.eclipse.jgit.treewalk", - org.eclipse.jgit.revwalk.filter;version="5.8.2"; + org.eclipse.jgit.revwalk.filter;version="5.9.1"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.lib, org.eclipse.jgit.util", - org.eclipse.jgit.storage.file;version="5.8.2"; + org.eclipse.jgit.storage.file;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.util", - org.eclipse.jgit.storage.pack;version="5.8.2"; + org.eclipse.jgit.storage.pack;version="5.9.1"; uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.submodule;version="5.8.2"; + org.eclipse.jgit.submodule;version="5.9.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.diff, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.treewalk, org.eclipse.jgit.util", - org.eclipse.jgit.transport;version="5.8.2"; + org.eclipse.jgit.transport;version="5.9.1"; uses:="javax.crypto, org.eclipse.jgit.util.io, org.eclipse.jgit.lib, @@ -190,21 +190,21 @@ Export-Package: org.eclipse.jgit.annotations;version="5.8.2", org.eclipse.jgit.transport.resolver, org.eclipse.jgit.storage.pack, org.eclipse.jgit.errors", - org.eclipse.jgit.transport.http;version="5.8.2"; + org.eclipse.jgit.transport.http;version="5.9.1"; uses:="javax.net.ssl", - org.eclipse.jgit.transport.resolver;version="5.8.2"; + org.eclipse.jgit.transport.resolver;version="5.9.1"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.lib", - org.eclipse.jgit.treewalk;version="5.8.2"; + org.eclipse.jgit.treewalk;version="5.9.1"; uses:="org.eclipse.jgit.dircache, org.eclipse.jgit.lib, org.eclipse.jgit.attributes, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util", - org.eclipse.jgit.treewalk.filter;version="5.8.2"; + org.eclipse.jgit.treewalk.filter;version="5.9.1"; uses:="org.eclipse.jgit.treewalk", - org.eclipse.jgit.util;version="5.8.2"; + org.eclipse.jgit.util;version="5.9.1"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.hooks, org.eclipse.jgit.revwalk, @@ -217,12 +217,12 @@ Export-Package: org.eclipse.jgit.annotations;version="5.8.2", org.eclipse.jgit.treewalk, javax.net.ssl, org.eclipse.jgit.util.time", - org.eclipse.jgit.util.io;version="5.8.2"; + org.eclipse.jgit.util.io;version="5.9.1"; uses:="org.eclipse.jgit.attributes, org.eclipse.jgit.lib, org.eclipse.jgit.treewalk", - org.eclipse.jgit.util.sha1;version="5.8.2", - org.eclipse.jgit.util.time;version="5.8.2" + org.eclipse.jgit.util.sha1;version="5.9.1", + org.eclipse.jgit.util.time;version="5.9.1" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", javax.crypto, diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF index 9202c879ed..c561b7ede1 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.8.2.qualifier -Eclipse-SourceBundle: org.eclipse.jgit;version="5.8.2.qualifier";roots="." +Bundle-Version: 5.9.1.qualifier +Eclipse-SourceBundle: org.eclipse.jgit;version="5.9.1.qualifier";roots="." diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml index baa74cba15..c899dfd293 100644 --- a/org.eclipse.jgit/pom.xml +++ b/org.eclipse.jgit/pom.xml @@ -20,7 +20,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>5.8.2-SNAPSHOT</version> + <version>5.9.1-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit</artifactId> diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index bf19fd9ba0..0e51eb45cb 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -202,10 +202,8 @@ countingObjects=Counting objects corruptPack=Pack file {0} is corrupt, removing it from pack list createBranchFailedUnknownReason=Create branch failed for unknown reason createBranchUnexpectedResult=Create branch returned unexpected result {0} -createJGitConfigFailed=Creating JGit config directory {} failed createNewFileFailed=Could not create new file {0} createRequiresZeroOldId=Create requires old ID to be zero -createXDGConfigHomeFailed=Creating XDG_CONFIG_HOME directory {} failed credentialPassword=Password credentialPassphrase=Passphrase credentialUsername=Username @@ -225,6 +223,8 @@ dirCacheDoesNotHaveABackingFile=DirCache does not have a backing file dirCacheFileIsNotLocked=DirCache {0} not locked dirCacheIsNotLocked=DirCache is not locked DIRCChecksumMismatch=DIRC checksum mismatch +DIRCCorruptLength=DIRC variable int {0} invalid after entry for {1} +DIRCCorruptLengthFirst=DIRC variable int {0} invalid in first entry DIRCExtensionIsTooLargeAt=DIRC extension {0} is too large at {1} bytes. DIRCExtensionNotSupportedByThisVersion=DIRC extension {0} not supported by this version. DIRCHasTooManyEntries=DIRC has too many entries. @@ -409,6 +409,10 @@ lockError=lock error: {0} lockFailedRetry=locking {0} failed after {1} retries lockOnNotClosed=Lock on {0} not closed. lockOnNotHeld=Lock on {0} not held. +logInconsistentFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: {} > {}, but diff = {}. Aborting measurement at resolution {}. +logLargerFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: diff = {} > {} (last good value). Aborting measurement. +logSmallerFiletime={}: got smaller file timestamp on {}, {}: {} < {}. Aborting measurement at resolution {}. +logXDGConfigHomeInvalid=Environment variable XDG_CONFIG_HOME contains an invalid path {} maxCountMustBeNonNegative=max count must be >= 0 mergeConflictOnNonNoteEntries=Merge conflict on non-note entries: base = {0}, ours = {1}, theirs = {2} mergeConflictOnNotes=Merge conflict on note {0}. base = {1}, ours = {2}, theirs = {2} @@ -622,6 +626,7 @@ sourceRefDoesntResolveToAnyObject=Source ref {0} doesn''t resolve to any object. sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0} squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD sshCommandFailed=Execution of ssh command ''{0}'' failed with error ''{1}'' +sshCommandTimeout=Execution of ssh command ''{0}'' timed out after {1} seconds sslFailureExceptionMessage=Secure connection to {0} could not be established because of SSL problems sslFailureInfo=A secure connection to {0} could not be established because the server''s certificate could not be validated. sslFailureCause=SSL reported: {0} @@ -662,7 +667,7 @@ threadInterruptedWhileRunning="Current thread interrupted while running {0}" timeIsUncertain=Time is uncertain timerAlreadyTerminated=Timer already terminated timeoutMeasureFsTimestampResolution=measuring filesystem timestamp resolution for ''{0}'' timed out, fall back to resolution of 2 seconds -tooManyCommands=Too many commands +tooManyCommands=Commands size exceeds limit defined in receive.maxCommandBytes tooManyFilters=Too many "filter" lines in request tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)? topologicalSortRequired=Topological sort required. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java index 7ae005ada8..1a41df3d0a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Google Inc. and others + * Copyright (C) 2010, 2020 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -123,7 +123,8 @@ public abstract class ContentSource { WorkingTreeIterator ptr; WorkingTreeSource(WorkingTreeIterator iterator) { - this.tw = new TreeWalk((ObjectReader) null); + this.tw = new TreeWalk(iterator.getRepository(), + (ObjectReader) null); this.tw.setRecursive(true); this.iterator = iterator; } @@ -173,6 +174,15 @@ public abstract class ContentSource { private void seek(String path) throws IOException { if (!path.equals(current)) { iterator.reset(); + // Possibly this iterator had an associated DirCacheIterator, + // but we have no access to it and thus don't know about it. + // We have to reset this iterator here to work without + // DirCacheIterator and to descend always into ignored + // directories. Otherwise we might not find tracked files below + // ignored folders. Since we're looking only for a single + // specific path this is not a performance problem. + iterator.setWalkIgnoredDirectories(true); + iterator.setDirCacheIterator(null, -1); tw.reset(); tw.addTree(iterator); tw.setFilter(PathFilter.create(path)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index 81367eaa08..ec21954aa2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de> and others + * Copyright (C) 2008-2020, Johannes E. Schindelin <johannes.schindelin@gmx.de> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -502,9 +502,18 @@ public class DiffFormatter implements AutoCloseable { throws IOException { assertHaveReader(); - TreeWalk walk = new TreeWalk(reader); - walk.addTree(a); - walk.addTree(b); + TreeWalk walk = new TreeWalk(repository, reader); + int aIndex = walk.addTree(a); + int bIndex = walk.addTree(b); + if (repository != null) { + if (a instanceof WorkingTreeIterator + && b instanceof DirCacheIterator) { + ((WorkingTreeIterator) a).setDirCacheIterator(walk, bIndex); + } else if (b instanceof WorkingTreeIterator + && a instanceof DirCacheIterator) { + ((WorkingTreeIterator) b).setDirCacheIterator(walk, aIndex); + } + } walk.setRecursive(true); TreeFilter filter = getDiffTreeFilterFor(a, b); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java index b2764d7804..03da61583d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -1,7 +1,7 @@ /* - * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2008, 2010, Google Inc. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> - * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com> and others + * Copyright (C) 2011, 2020, Matthias Sohn <matthias.sohn@sap.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -41,6 +41,9 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.FileSnapshot; import org.eclipse.jgit.internal.storage.file.LockFile; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Config.ConfigEnum; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -321,6 +324,9 @@ public class DirCache { /** Repository containing this index */ private Repository repository; + /** If we read this index from disk, the original format. */ + private DirCacheVersion version; + /** * Create a new in-core index representation. * <p> @@ -364,6 +370,10 @@ public class DirCache { return new DirCacheEditor(this, entryCnt + 16); } + DirCacheVersion getVersion() { + return version; + } + void replace(DirCacheEntry[] e, int cnt) { sortedEntries = e; entryCnt = cnt; @@ -445,13 +455,26 @@ public class DirCache { md.update(hdr, 0, 12); if (!is_DIRC(hdr)) throw new CorruptObjectException(JGitText.get().notADIRCFile); - final int ver = NB.decodeInt32(hdr, 4); + int versionCode = NB.decodeInt32(hdr, 4); + DirCacheVersion ver = DirCacheVersion.fromInt(versionCode); + if (ver == null) { + throw new CorruptObjectException( + MessageFormat.format(JGitText.get().unknownDIRCVersion, + Integer.valueOf(versionCode))); + } boolean extended = false; - if (ver == 3) + switch (ver) { + case DIRC_VERSION_MINIMUM: + break; + case DIRC_VERSION_EXTENDED: + case DIRC_VERSION_PATHCOMPRESS: extended = true; - else if (ver != 2) - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().unknownDIRCVersion, Integer.valueOf(ver))); + break; + default: + throw new CorruptObjectException(MessageFormat + .format(JGitText.get().unknownDIRCVersion, ver)); + } + version = ver; entryCnt = NB.decodeInt32(hdr, 8); if (entryCnt < 0) throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries); @@ -467,7 +490,8 @@ public class DirCache { final MutableInteger infoAt = new MutableInteger(); for (int i = 0; i < entryCnt; i++) { - sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge); + sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge, + version, i == 0 ? null : sortedEntries[i - 1]); } // After the file entries are index extensions, and then a footer. @@ -606,11 +630,20 @@ public class DirCache { final MessageDigest foot = Constants.newMessageDigest(); final DigestOutputStream dos = new DigestOutputStream(os, foot); - boolean extended = false; - for (int i = 0; i < entryCnt; i++) { - if (sortedEntries[i].isExtended()) { - extended = true; - break; + if (version == null && this.repository != null) { + // A new DirCache is being written. + DirCacheConfig config = repository.getConfig() + .get(DirCacheConfig::new); + version = config.getIndexVersion(); + } + if (version == null + || version == DirCacheVersion.DIRC_VERSION_MINIMUM) { + version = DirCacheVersion.DIRC_VERSION_MINIMUM; + for (int i = 0; i < entryCnt; i++) { + if (sortedEntries[i].isExtended()) { + version = DirCacheVersion.DIRC_VERSION_EXTENDED; + break; + } } } @@ -618,7 +651,7 @@ public class DirCache { // final byte[] tmp = new byte[128]; System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length); - NB.encodeInt32(tmp, 4, extended ? 3 : 2); + NB.encodeInt32(tmp, 4, version.getVersionCode()); NB.encodeInt32(tmp, 8, entryCnt); dos.write(tmp, 0, 12); @@ -650,7 +683,7 @@ public class DirCache { if (e.mightBeRacilyClean(smudge)) { e.smudgeRacilyClean(); } - e.write(dos); + e.write(dos, version, i == 0 ? null : sortedEntries[i - 1]); } if (writeTree) { @@ -982,4 +1015,76 @@ public class DirCache { } } } + + enum DirCacheVersion implements ConfigEnum { + + /** Minimum index version on-disk format that we support. */ + DIRC_VERSION_MINIMUM(2), + /** Version 3 supports extended flags. */ + DIRC_VERSION_EXTENDED(3), + /** + * Version 4 adds very simple "path compression": it strips out the + * common prefix between the last entry written and the current entry. + * Instead of writing two entries with paths "foo/bar/baz/a.txt" and + * "foo/bar/baz/b.txt" it only writes "b.txt" for the second entry. + * <p> + * It is also <em>not</em> padded. + * </p> + */ + DIRC_VERSION_PATHCOMPRESS(4); + + private final int version; + + private DirCacheVersion(int versionCode) { + this.version = versionCode; + } + + public int getVersionCode() { + return version; + } + + @Override + public String toConfigValue() { + return Integer.toString(version); + } + + @Override + public boolean matchConfigValue(String in) { + try { + return version == Integer.parseInt(in); + } catch (NumberFormatException e) { + return false; + } + } + + public static DirCacheVersion fromInt(int val) { + for (DirCacheVersion v : DirCacheVersion.values()) { + if (val == v.getVersionCode()) { + return v; + } + } + return null; + } + } + + private static class DirCacheConfig { + + private final DirCacheVersion indexVersion; + + public DirCacheConfig(Config cfg) { + boolean manyFiles = cfg.getBoolean( + ConfigConstants.CONFIG_FEATURE_SECTION, + ConfigConstants.CONFIG_KEY_MANYFILES, false); + indexVersion = cfg.getEnum(DirCacheVersion.values(), + ConfigConstants.CONFIG_INDEX_SECTION, null, + ConfigConstants.CONFIG_KEY_VERSION, + manyFiles ? DirCacheVersion.DIRC_VERSION_PATHCOMPRESS + : DirCacheVersion.DIRC_VERSION_EXTENDED); + } + + public DirCacheVersion getIndexVersion() { + return indexVersion; + } + + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java index ced379ff1d..dcb84825fe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java @@ -1,8 +1,8 @@ /* - * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, 2009, Google Inc. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com> - * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others + * Copyright (C) 2010, 2020, Christian Halstrick <christian.halstrick@sap.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -26,6 +26,7 @@ import java.text.MessageFormat; import java.time.Instant; import java.util.Arrays; +import org.eclipse.jgit.dircache.DirCache.DirCacheVersion; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; @@ -112,15 +113,16 @@ public class DirCacheEntry { /** Flags which are never stored to disk. */ private byte inCoreFlags; - DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, - final InputStream in, final MessageDigest md, final Instant smudge) + DirCacheEntry(byte[] sharedInfo, MutableInteger infoAt, InputStream in, + MessageDigest md, Instant smudge, DirCacheVersion version, + DirCacheEntry previous) throws IOException { info = sharedInfo; infoOffset = infoAt.value; IO.readFully(in, info, infoOffset, INFO_LEN); - final int len; + int len; if (isExtended()) { len = INFO_LEN_EXTENDED; IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN); @@ -134,31 +136,66 @@ public class DirCacheEntry { infoAt.value += len; md.update(info, infoOffset, len); + int toRemove = 0; + if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) { + // Read variable int and update digest + int b = in.read(); + md.update((byte) b); + toRemove = b & 0x7F; + while ((b & 0x80) != 0) { + toRemove++; + b = in.read(); + md.update((byte) b); + toRemove = (toRemove << 7) | (b & 0x7F); + } + if (toRemove < 0 + || (previous != null && toRemove > previous.path.length)) { + if (previous == null) { + throw new IOException(MessageFormat.format( + JGitText.get().DIRCCorruptLengthFirst, + Integer.valueOf(toRemove))); + } + throw new IOException(MessageFormat.format( + JGitText.get().DIRCCorruptLength, + Integer.valueOf(toRemove), previous.getPathString())); + } + } int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK; int skipped = 0; if (pathLen < NAME_MASK) { path = new byte[pathLen]; - IO.readFully(in, path, 0, pathLen); - md.update(path, 0, pathLen); - } else { - final ByteArrayOutputStream tmp = new ByteArrayOutputStream(); - { - final byte[] buf = new byte[NAME_MASK]; - IO.readFully(in, buf, 0, NAME_MASK); - tmp.write(buf); - } - for (;;) { - final int c = in.read(); - if (c < 0) - throw new EOFException(JGitText.get().shortReadOfBlock); - if (c == 0) - break; - tmp.write(c); + if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS + && previous != null) { + System.arraycopy(previous.path, 0, path, 0, + previous.path.length - toRemove); + IO.readFully(in, path, previous.path.length - toRemove, + pathLen - (previous.path.length - toRemove)); + md.update(path, previous.path.length - toRemove, + pathLen - (previous.path.length - toRemove)); + pathLen = pathLen - (previous.path.length - toRemove); + } else { + IO.readFully(in, path, 0, pathLen); + md.update(path, 0, pathLen); } + } else if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS + || previous == null || toRemove == previous.path.length) { + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + byte[] buf = new byte[NAME_MASK]; + IO.readFully(in, buf, 0, NAME_MASK); + tmp.write(buf); + readNulTerminatedString(in, tmp); path = tmp.toByteArray(); pathLen = path.length; - skipped = 1; // we already skipped 1 '\0' above to break the loop. md.update(path, 0, pathLen); + skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString + md.update((byte) 0); + } else { + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + tmp.write(previous.path, 0, previous.path.length - toRemove); + pathLen = readNulTerminatedString(in, tmp); + path = tmp.toByteArray(); + md.update(path, previous.path.length - toRemove, pathLen); + skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString md.update((byte) 0); } @@ -172,17 +209,26 @@ public class DirCacheEntry { throw p; } - // Index records are padded out to the next 8 byte alignment - // for historical reasons related to how C Git read the files. - // - final int actLen = len + pathLen; - final int expLen = (actLen + 8) & ~7; - final int padLen = expLen - actLen - skipped; - if (padLen > 0) { - IO.skipFully(in, padLen); - md.update(nullpad, 0, padLen); + if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) { + if (skipped == 0) { + int b = in.read(); + if (b < 0) { + throw new EOFException(JGitText.get().shortReadOfBlock); + } + md.update((byte) b); + } + } else { + // Index records are padded out to the next 8 byte alignment + // for historical reasons related to how C Git read the files. + // + final int actLen = len + pathLen; + final int expLen = (actLen + 8) & ~7; + final int padLen = expLen - actLen - skipped; + if (padLen > 0) { + IO.skipFully(in, padLen); + md.update(nullpad, 0, padLen); + } } - if (mightBeRacilyClean(smudge)) { smudgeRacilyClean(); } @@ -283,19 +329,61 @@ public class DirCacheEntry { System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN); } - void write(OutputStream os) throws IOException { - final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN; - final int pathLen = path.length; - os.write(info, infoOffset, len); - os.write(path, 0, pathLen); + private int readNulTerminatedString(InputStream in, OutputStream out) + throws IOException { + int n = 0; + for (;;) { + int c = in.read(); + if (c < 0) { + throw new EOFException(JGitText.get().shortReadOfBlock); + } + if (c == 0) { + break; + } + out.write(c); + n++; + } + return n; + } - // Index records are padded out to the next 8 byte alignment - // for historical reasons related to how C Git read the files. - // - final int actLen = len + pathLen; - final int expLen = (actLen + 8) & ~7; - if (actLen != expLen) - os.write(nullpad, 0, expLen - actLen); + void write(OutputStream os, DirCacheVersion version, DirCacheEntry previous) + throws IOException { + final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN; + if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) { + os.write(info, infoOffset, len); + os.write(path, 0, path.length); + // Index records are padded out to the next 8 byte alignment + // for historical reasons related to how C Git read the files. + // + int entryLen = len + path.length; + int expLen = (entryLen + 8) & ~7; + if (entryLen != expLen) + os.write(nullpad, 0, expLen - entryLen); + } else { + int pathCommon = 0; + int toRemove; + if (previous != null) { + // Figure out common prefix + int pathLen = Math.min(path.length, previous.path.length); + while (pathCommon < pathLen + && path[pathCommon] == previous.path[pathCommon]) { + pathCommon++; + } + toRemove = previous.path.length - pathCommon; + } else { + toRemove = 0; + } + byte[] tmp = new byte[16]; + int n = tmp.length; + tmp[--n] = (byte) (toRemove & 0x7F); + while ((toRemove >>>= 7) != 0) { + tmp[--n] = (byte) (0x80 | (--toRemove & 0x7F)); + } + os.write(info, infoOffset, len); + os.write(tmp, n, tmp.length - n); + os.write(path, pathCommon, path.length - pathCommon); + os.write(0); + } } /** 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 d04887c4dd..7ec5eaea10 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -230,10 +230,8 @@ public class JGitText extends TranslationBundle { /***/ public String countingObjects; /***/ public String createBranchFailedUnknownReason; /***/ public String createBranchUnexpectedResult; - /***/ public String createJGitConfigFailed; /***/ public String createNewFileFailed; /***/ public String createRequiresZeroOldId; - /***/ public String createXDGConfigHomeFailed; /***/ public String credentialPassword; /***/ public String credentialPassphrase; /***/ public String credentialUsername; @@ -253,6 +251,8 @@ public class JGitText extends TranslationBundle { /***/ public String dirCacheFileIsNotLocked; /***/ public String dirCacheIsNotLocked; /***/ public String DIRCChecksumMismatch; + /***/ public String DIRCCorruptLength; + /***/ public String DIRCCorruptLengthFirst; /***/ public String DIRCExtensionIsTooLargeAt; /***/ public String DIRCExtensionNotSupportedByThisVersion; /***/ public String DIRCHasTooManyEntries; @@ -437,6 +437,10 @@ public class JGitText extends TranslationBundle { /***/ public String lockFailedRetry; /***/ public String lockOnNotClosed; /***/ public String lockOnNotHeld; + /***/ public String logInconsistentFiletimeDiff; + /***/ public String logLargerFiletimeDiff; + /***/ public String logSmallerFiletime; + /***/ public String logXDGConfigHomeInvalid; /***/ public String maxCountMustBeNonNegative; /***/ public String mergeConflictOnNonNoteEntries; /***/ public String mergeConflictOnNotes; @@ -650,6 +654,7 @@ public class JGitText extends TranslationBundle { /***/ public String sourceRefNotSpecifiedForRefspec; /***/ public String squashCommitNotUpdatingHEAD; /***/ public String sshCommandFailed; + /***/ public String sshCommandTimeout; /***/ public String sslFailureExceptionMessage; /***/ public String sslFailureInfo; /***/ public String sslFailureCause; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java new file mode 100644 index 0000000000..736f381d78 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.internal.storage.dfs; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.internal.storage.pack.CachedPack; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.transport.BundleWriter; + +/** Writes {@link DfsRepository} to a Git bundle. */ +public class DfsBundleWriter { + /** + * Writes the entire {@link DfsRepository} to a Git bundle. + * <p> + * This method try to avoid traversing the pack files as much as possible + * and dumps all objects as-is to a Git bundle. + * + * @param pm + * progress monitor + * @param os + * Git bundle output + * @param db + * repository + * @throws IOException + * thrown if the output stream throws one. + */ + public static void writeEntireRepositoryAsBundle(ProgressMonitor pm, + OutputStream os, DfsRepository db) throws IOException { + BundleWriter bw = new BundleWriter(db); + db.getRefDatabase().getRefs().forEach(bw::include); + List<CachedPack> packs = new ArrayList<>(); + for (DfsPackFile p : db.getObjectDatabase().getPacks()) { + packs.add(new DfsCachedPack(p)); + } + bw.addObjectsAsIs(packs); + bw.writeBundle(pm, os); + } + + private DfsBundleWriter() { + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java index c9bb167f02..ec53818b4e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java @@ -59,12 +59,26 @@ abstract class BasePackBitmapIndex extends PackBitmapIndex { * @return the full bitmap */ EWAHCompressedBitmap getBitmap() { + EWAHCompressedBitmap bitmap = getBitmapWithoutCaching(); + // Cache the result. + bitmapContainer = bitmap; + return bitmap; + } + + /** + * Compute and return the full bitmap, do NOT cache the expanded bitmap, + * which saves memory and should only be used during bitmap creation in + * garbage collection. + * + * @return the full bitmap + */ + EWAHCompressedBitmap getBitmapWithoutCaching() { // Fast path to immediately return the expanded result. Object r = bitmapContainer; if (r instanceof EWAHCompressedBitmap) return (EWAHCompressedBitmap) r; - // Expand the bitmap and cache the result. + // Expand the bitmap but not cache the result. XorCompressedBitmap xb = (XorCompressedBitmap) r; EWAHCompressedBitmap out = xb.bitmap; for (;;) { @@ -72,7 +86,6 @@ abstract class BasePackBitmapIndex extends PackBitmapIndex { if (r instanceof EWAHCompressedBitmap) { out = out.xor((EWAHCompressedBitmap) r); out.trim(); - bitmapContainer = out; return out; } xb = (XorCompressedBitmap) r; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java index 4b25284517..dd5d03c6e9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java @@ -156,7 +156,8 @@ public class PackBitmapIndexRemapper extends PackBitmapIndex return null; inflated.clear(); - for (IntIterator i = oldBitmap.getBitmap().intIterator(); i.hasNext();) + for (IntIterator i = oldBitmap.getBitmapWithoutCaching() + .intIterator(); i.hasNext();) inflated.set(prevToNewMapping[i.next()]); bitmap = inflated.toEWAHCompressedBitmap(); bitmap.trim(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java index 9cf95d0720..eb0ac6a062 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java @@ -49,11 +49,11 @@ class PackIndexV1 extends PackIndex { idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4); idxdata = new byte[idxHeader.length][]; for (int k = 0; k < idxHeader.length; k++) { - int n; + long n; if (k == 0) { - n = (int) (idxHeader[k]); + n = idxHeader[k]; } else { - n = (int) (idxHeader[k] - idxHeader[k - 1]); + n = idxHeader[k] - idxHeader[k - 1]; } if (n > 0) { final long len = n * (Constants.OBJECT_ID_LENGTH + 4); 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 80c8e10dec..3e8cb3a3f2 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 @@ -1,6 +1,6 @@ /* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others + * Copyright (C) 2008, 2009 Google Inc. + * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.atomic.LongAdder; @@ -376,14 +377,14 @@ public class WindowCache { * @return the cached instance. */ public static WindowCache getInstance() { - return cache; + return cache.publishMBeanIfNeeded(); } static final ByteWindow get(PackFile pack, long offset) throws IOException { final WindowCache c = cache; final ByteWindow r = c.getOrLoad(pack, c.toStart(offset)); - if (c != cache) { + if (c != cache.publishMBeanIfNeeded()) { // The cache was reconfigured while we were using the old one // to load this window. The window is still valid, but our // cache may think its still live. Ensure the window is removed @@ -433,6 +434,8 @@ public class WindowCache { private final StatsRecorderImpl mbean; + private final AtomicBoolean publishMBean = new AtomicBoolean(); + private boolean useStrongRefs; private WindowCache(WindowCacheConfig cfg) { @@ -470,9 +473,7 @@ public class WindowCache { mbean = new StatsRecorderImpl(); statsRecorder = mbean; - if (cfg.getExposeStatsViaJmx()) { - Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$ - } + publishMBean.set(cfg.getExposeStatsViaJmx()); if (maxFiles < 1) throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1); @@ -480,6 +481,13 @@ public class WindowCache { throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit); } + private WindowCache publishMBeanIfNeeded() { + if (publishMBean.getAndSet(false)) { + Monitoring.registerMBean(mbean, "block_cache"); //$NON-NLS-1$ + } + return this; + } + /** * @return cache statistics for the WindowCache */ 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 824c62ad9a..3e4b5df6aa 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 @@ -756,6 +756,19 @@ public class PackWriter implements AutoCloseable { /** * Prepare the list of objects to be written to the pack stream. + * + * <p> + * PackWriter will concat and write out the specified packs as-is. + * + * @param c + * cached packs to be written. + */ + public void preparePack(Collection<? extends CachedPack> c) { + cachedPacks.addAll(c); + } + + /** + * Prepare the list of objects to be written to the pack stream. * <p> * Basing on these 2 sets, another set of objects to put in a pack file is * created: this set consists of all objects reachable (ancestors) from @@ -1548,6 +1561,7 @@ public class PackWriter implements AutoCloseable { endPhase(monitor); } + @SuppressWarnings("Finally") private void parallelDeltaSearch(ProgressMonitor monitor, ObjectToPack[] list, int cnt, int threads) throws IOException { DeltaCache dc = new ThreadSafeDeltaCache(config); @@ -1569,15 +1583,22 @@ public class PackWriter implements AutoCloseable { // Caller didn't give us a way to run the tasks, spawn up a // temporary thread pool and make sure it tears down cleanly. ExecutorService pool = Executors.newFixedThreadPool(threads); + Throwable e1 = null; try { runTasks(pool, pm, taskBlock, errors); + } catch (Exception e) { + e1 = e; } finally { pool.shutdown(); for (;;) { try { - if (pool.awaitTermination(60, TimeUnit.SECONDS)) + if (pool.awaitTermination(60, TimeUnit.SECONDS)) { break; + } } catch (InterruptedException e) { + if (e1 != null) { + e.addSuppressed(e1); + } throw new IOException(JGitText .get().packingCancelledDuringObjectsWriting, e); } @@ -2182,10 +2203,12 @@ public class PackWriter implements AutoCloseable { // Check if this object needs to be rejected, doing the cheaper // checks first. - boolean reject = filterSpec.getBlobLimit() >= 0 && - type == OBJ_BLOB && - !want.contains(src) && - reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit(); + boolean reject = + (!filterSpec.allowsType(type) && !want.contains(src)) || + (filterSpec.getBlobLimit() >= 0 && + type == OBJ_BLOB && + !want.contains(src) && + reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit()); if (!reject) { addObject(src, type, pathHashCode); } 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 36335153a1..a78f4d24da 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 @@ -82,7 +82,7 @@ public class MergedReftable extends Reftable { return 0; } long minUpdateIndex = tables[0].minUpdateIndex(); - for (int i = 0; i < tables.length - 1; i++) { + for (int i = 1; i < tables.length; i++) { if (tables[i].minUpdateIndex() < minUpdateIndex) { minUpdateIndex = tables[i].minUpdateIndex(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java index f6695bdf7d..b11b230a8f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java @@ -86,7 +86,7 @@ public interface BitmapIndex { /** * Returns the corresponding raw compressed EWAH bitmap of the bitmap. - * + * * @return the corresponding {@code EWAHCompressedBitmap} * @since 5.8 */ 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 66d7d51bdf..4f93fda49f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java @@ -361,7 +361,9 @@ public class CommitBuilder { * 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. + * indent of one space starting with line two. A trailing line break is + * <em>not</em> written; the caller is supposed to terminate the GPG + * signature header by writing a single newline. * </p> * * @param in @@ -375,22 +377,24 @@ public class CommitBuilder { */ static void writeGpgSignatureString(String in, OutputStream out) throws IOException, IllegalArgumentException { - for (int i = 0; i < in.length(); ++i) { + int length = in.length(); + for (int i = 0; i < length; ++i) { char ch = in.charAt(i); switch (ch) { case '\r': - if (i + 1 < in.length() && in.charAt(i + 1) == '\n') { - out.write('\n'); - out.write(' '); + if (i + 1 < length && in.charAt(i + 1) == '\n') { ++i; - } else { + } + if (i + 1 < length) { out.write('\n'); out.write(' '); } break; case '\n': - out.write('\n'); - out.write(' '); + if (i + 1 < length) { + out.write('\n'); + out.write(' '); + } break; default: // sanity check diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index eef822fa4b..834fff5dd9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com> * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> - * Copyright (C) 2012-2013, Robin Rosenberg and others + * Copyright (C) 2012, 2020, Robin Rosenberg and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -93,6 +93,12 @@ public final class ConfigConstants { public static final String CONFIG_GPG_SECTION = "gpg"; /** + * The "protocol" section + * @since 5.9 + */ + public static final String CONFIG_PROTOCOL_SECTION = "protocol"; + + /** * The "format" key * @since 5.2 */ @@ -662,4 +668,33 @@ public final class ConfigConstants { * @since 5.8 */ public static final String CONFIG_KEY_WINDOW_MEMORY = "windowmemory"; + + /** + * The "feature" section + * + * @since 5.9 + */ + public static final String CONFIG_FEATURE_SECTION = "feature"; + + /** + * The "feature.manyFiles" key + * + * @since 5.9 + */ + public static final String CONFIG_KEY_MANYFILES = "manyFiles"; + + /** + * The "index" section + * + * @since 5.9 + */ + public static final String CONFIG_INDEX_SECTION = "index"; + + /** + * The "version" key + * + * @since 5.9 + */ + public static final String CONFIG_KEY_VERSION = "version"; + } 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 506d333120..6c217fdf25 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -588,7 +588,8 @@ public class ResolveMerger extends ThreeWayMerger { final int modeO = tw.getRawMode(T_OURS); final int modeT = tw.getRawMode(T_THEIRS); final int modeB = tw.getRawMode(T_BASE); - + boolean gitLinkMerging = isGitLink(modeO) || isGitLink(modeT) + || isGitLink(modeB); if (modeO == 0 && modeT == 0 && modeB == 0) // File is either untracked or new, staged but uncommitted return true; @@ -737,31 +738,28 @@ public class ResolveMerger extends ThreeWayMerger { return false; } - boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT); - // Don't attempt to resolve submodule link conflicts - if (gitlinkConflict || !attributes.canBeContentMerged()) { + if (gitLinkMerging && ignoreConflicts) { + // Always select 'ours' in case of GITLINK merge failures so + // a caller can use virtual commit. + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0); + return true; + } else if (gitLinkMerging) { + add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); + add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); + MergeResult<SubmoduleConflict> result = createGitLinksMergeResult( + base, ours, theirs); + result.setContainsConflicts(true); + mergeResults.put(tw.getPathString(), result); + unmergedPaths.add(tw.getPathString()); + return true; + } else if (!attributes.canBeContentMerged()) { add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); - if (gitlinkConflict) { - MergeResult<SubmoduleConflict> result = new MergeResult<>( - Arrays.asList( - new SubmoduleConflict(base == null ? null - : base.getEntryObjectId()), - new SubmoduleConflict(ours == null ? null - : ours.getEntryObjectId()), - new SubmoduleConflict(theirs == null ? null - : theirs.getEntryObjectId()))); - result.setContainsConflicts(true); - mergeResults.put(tw.getPathString(), result); - if (!ignoreConflicts) { - unmergedPaths.add(tw.getPathString()); - } - } else { - // attribute merge issues are conflicts but not failures - unmergedPaths.add(tw.getPathString()); - } + // attribute merge issues are conflicts but not failures + unmergedPaths.add(tw.getPathString()); return true; } @@ -786,45 +784,73 @@ public class ResolveMerger extends ThreeWayMerger { // OURS or THEIRS has been deleted if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw .idEqual(T_BASE, T_THEIRS)))) { - MergeResult<RawText> result = contentMerge(base, ours, theirs, - attributes); - - if (ignoreConflicts) { - // In case a conflict is detected the working tree file is - // again filled with new content (containing conflict - // markers). But also stage 0 of the index is filled with - // that content. - result.setContainsConflicts(false); - updateIndex(base, ours, theirs, result, attributes); - } else { + if (gitLinkMerging && ignoreConflicts) { + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0); + } else if (gitLinkMerging) { add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); - DirCacheEntry e = add(tw.getRawPath(), theirs, - DirCacheEntry.STAGE_3, EPOCH, 0); - - // OURS was deleted checkout THEIRS - if (modeO == 0) { - // Check worktree before checking out THEIRS - if (isWorktreeDirty(work, ourDce)) { - return false; - } - if (nonTree(modeT)) { - if (e != null) { - addToCheckout(tw.getPathString(), e, attributes); + add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); + MergeResult<SubmoduleConflict> result = createGitLinksMergeResult( + base, ours, theirs); + result.setContainsConflicts(true); + mergeResults.put(tw.getPathString(), result); + unmergedPaths.add(tw.getPathString()); + } else { + MergeResult<RawText> result = contentMerge(base, ours, + theirs, attributes); + + if (ignoreConflicts) { + // In case a conflict is detected the working tree file + // is again filled with new content (containing conflict + // markers). But also stage 0 of the index is filled + // with that content. + result.setContainsConflicts(false); + updateIndex(base, ours, theirs, result, attributes); + } else { + add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, + 0); + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, + 0); + DirCacheEntry e = add(tw.getRawPath(), theirs, + DirCacheEntry.STAGE_3, EPOCH, 0); + + // OURS was deleted checkout THEIRS + if (modeO == 0) { + // Check worktree before checking out THEIRS + if (isWorktreeDirty(work, ourDce)) { + return false; + } + if (nonTree(modeT)) { + if (e != null) { + addToCheckout(tw.getPathString(), e, + attributes); + } } } - } - unmergedPaths.add(tw.getPathString()); + unmergedPaths.add(tw.getPathString()); - // generate a MergeResult for the deleted file - mergeResults.put(tw.getPathString(), result); + // generate a MergeResult for the deleted file + mergeResults.put(tw.getPathString(), result); + } } } } return true; } + private static MergeResult<SubmoduleConflict> createGitLinksMergeResult( + CanonicalTreeParser base, CanonicalTreeParser ours, + CanonicalTreeParser theirs) { + return new MergeResult<>(Arrays.asList( + new SubmoduleConflict( + base == null ? null : base.getEntryObjectId()), + new SubmoduleConflict( + ours == null ? null : ours.getEntryObjectId()), + new SubmoduleConflict( + theirs == null ? null : theirs.getEntryObjectId()))); + } + /** * Does the content merge. The three texts base, ours and theirs are * specified with {@link CanonicalTreeParser}. If any of the parsers is diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java index 57eed3ad2a..4649d33ff8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -17,12 +17,16 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.pack.CachedPack; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; @@ -62,6 +66,8 @@ public class BundleWriter { private final Set<ObjectId> tagTargets; + private final List<CachedPack> cachedPacks = new ArrayList<>(); + private PackConfig packConfig; private ObjectCountCallback callback; @@ -150,6 +156,26 @@ public class BundleWriter { } /** + * Add objects to the bundle file. + * + * <p> + * When this method is used, object traversal is disabled and specified pack + * files are directly saved to the Git bundle file. + * + * <p> + * Unlike {@link #include}, this doesn't affect the refs. Even if the + * objects are not reachable from any ref, they will be included in the + * bundle file. + * + * @param c + * pack to include + * @since 5.9 + */ + public void addObjectsAsIs(Collection<? extends CachedPack> c) { + cachedPacks.addAll(c); + } + + /** * Assume a commit is available on the recipient's side. * <p> * In order to fetch from a bundle the recipient must have any assumed @@ -187,19 +213,24 @@ public class BundleWriter { try (PackWriter packWriter = newPackWriter()) { packWriter.setObjectCountCallback(callback); - final HashSet<ObjectId> inc = new HashSet<>(); - final HashSet<ObjectId> exc = new HashSet<>(); - inc.addAll(include.values()); - for (RevCommit r : assume) - exc.add(r.getId()); packWriter.setIndexDisabled(true); packWriter.setDeltaBaseAsOffset(true); - packWriter.setThin(!exc.isEmpty()); packWriter.setReuseValidatingObjects(false); - if (exc.isEmpty()) { - packWriter.setTagTargets(tagTargets); + if (cachedPacks.isEmpty()) { + HashSet<ObjectId> inc = new HashSet<>(); + HashSet<ObjectId> exc = new HashSet<>(); + inc.addAll(include.values()); + for (RevCommit r : assume) { + exc.add(r.getId()); + } + if (exc.isEmpty()) { + packWriter.setTagTargets(tagTargets); + } + packWriter.setThin(!exc.isEmpty()); + packWriter.preparePack(monitor, inc, exc); + } else { + packWriter.preparePack(cachedPacks); } - packWriter.preparePack(monitor, inc, exc); final Writer w = new OutputStreamWriter(os, UTF_8); w.write(TransportBundle.V2_BUNDLE_SIGNATURE); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java index d09b5579fa..a8cf849fed 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java @@ -10,6 +10,15 @@ package org.eclipse.jgit.transport; +import static java.math.BigInteger.ZERO; +import static java.util.Objects.requireNonNull; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; + +import java.math.BigInteger; import java.text.MessageFormat; import org.eclipse.jgit.annotations.Nullable; @@ -24,11 +33,54 @@ import org.eclipse.jgit.internal.JGitText; */ public final class FilterSpec { + /** Immutable bit-set representation of a set of Git object types. */ + static class ObjectTypes { + static ObjectTypes ALL = allow(OBJ_BLOB, OBJ_TREE, OBJ_COMMIT, OBJ_TAG); + + private final BigInteger val; + + private ObjectTypes(BigInteger val) { + this.val = requireNonNull(val); + } + + static ObjectTypes allow(int... types) { + BigInteger bits = ZERO; + for (int type : types) { + bits = bits.setBit(type); + } + return new ObjectTypes(bits); + } + + boolean contains(int type) { + return val.testBit(type); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ObjectTypes)) { + return false; + } + + ObjectTypes other = (ObjectTypes) obj; + return other.val.equals(val); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return val.hashCode(); + } + } + + private final ObjectTypes types; + private final long blobLimit; private final long treeDepthLimit; - private FilterSpec(long blobLimit, long treeDepthLimit) { + private FilterSpec(ObjectTypes types, long blobLimit, long treeDepthLimit) { + this.types = requireNonNull(types); this.blobLimit = blobLimit; this.treeDepthLimit = treeDepthLimit; } @@ -53,7 +105,8 @@ public final class FilterSpec { public static FilterSpec fromFilterLine(String filterLine) throws PackProtocolException { if (filterLine.equals("blob:none")) { //$NON-NLS-1$ - return FilterSpec.withBlobLimit(0); + return FilterSpec.withObjectTypes( + ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG)); } else if (filterLine.startsWith("blob:limit=")) { //$NON-NLS-1$ long blobLimit = -1; try { @@ -86,8 +139,18 @@ public final class FilterSpec { } /** + * @param types + * set of permitted object types, for use in "blob:none" and + * "object:none" filters + * @return a filter spec which restricts to objects of the specified types + */ + static FilterSpec withObjectTypes(ObjectTypes types) { + return new FilterSpec(types, -1, -1); + } + + /** * @param blobLimit - * the blob limit in a "blob:[limit]" or "blob:none" filter line + * the blob limit in a "blob:[limit]" filter line * @return a filter spec which filters blobs above a certain size */ static FilterSpec withBlobLimit(long blobLimit) { @@ -95,7 +158,7 @@ public final class FilterSpec { throw new IllegalArgumentException( "blobLimit cannot be negative: " + blobLimit); //$NON-NLS-1$ } - return new FilterSpec(blobLimit, -1); + return new FilterSpec(ObjectTypes.ALL, blobLimit, -1); } /** @@ -109,13 +172,25 @@ public final class FilterSpec { throw new IllegalArgumentException( "treeDepthLimit cannot be negative: " + treeDepthLimit); //$NON-NLS-1$ } - return new FilterSpec(-1, treeDepthLimit); + return new FilterSpec(ObjectTypes.ALL, -1, treeDepthLimit); } /** * A placeholder that indicates no filtering. */ - public static final FilterSpec NO_FILTER = new FilterSpec(-1, -1); + public static final FilterSpec NO_FILTER = new FilterSpec(ObjectTypes.ALL, -1, -1); + + /** + * @param type + * a Git object type, such as + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB} + * @return whether this filter allows objects of the specified type + * + * @since 5.9 + */ + public boolean allowsType(int type) { + return types.contains(type); + } /** * @return -1 if this filter does not filter blobs based on size, or a @@ -138,7 +213,7 @@ public final class FilterSpec { * @return true if this filter doesn't filter out anything */ public boolean isNoOp() { - return blobLimit == -1 && treeDepthLimit == -1; + return types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit == -1; } /** @@ -146,14 +221,17 @@ public final class FilterSpec { */ @Nullable public String filterLine() { - if (blobLimit == 0) { - return GitProtocolConstants.OPTION_FILTER + " blob:none"; //$NON-NLS-1$ + if (isNoOp()) { + return null; + } else if (types.equals(ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG)) && + blobLimit == -1 && treeDepthLimit == -1) { + return OPTION_FILTER + " blob:none"; //$NON-NLS-1$ + } else if (types.equals(ObjectTypes.ALL) && blobLimit >= 0 && treeDepthLimit == -1) { + return OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$ + } else if (types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit >= 0) { + return OPTION_FILTER + " tree:" + treeDepthLimit; //$NON-NLS-1$ + } else { + throw new IllegalStateException(); } - - if (blobLimit > 0) { - return GitProtocolConstants.OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$ - } - - return null; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java index f9f50d46e7..dd4c967f91 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java @@ -4,7 +4,7 @@ * Copyright (C) 2009, Google, Inc. * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others + * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -18,26 +18,20 @@ package org.eclipse.jgit.transport; import java.io.IOException; /** - * Create a remote "session" for executing remote commands. - * <p> - * Clients should subclass RemoteSession to create an alternate way for JGit to - * execute remote commands. (The client application may already have this - * functionality available.) Note that this class is just a factory for creating - * remote processes. If the application already has a persistent connection to - * the remote machine, RemoteSession may do nothing more than return a new - * RemoteProcess when exec is called. + * An abstraction of a remote "session" for executing remote commands. */ public interface RemoteSession { + /** - * Generate a new remote process to execute the given command. This function - * should also start execution and may need to create the streams prior to - * execution. + * Creates a new remote {@link Process} to execute the given command. The + * returned process's streams exist and are connected, and execution of the + * process is already started. * * @param commandName * command to execute * @param timeout - * timeout value, in seconds, for command execution - * @return a new remote process + * timeout value, in seconds, for creating the remote process + * @return a new remote process, already started * @throws java.io.IOException * may be thrown in several cases. For example, on problems * opening input or output streams or on problems connecting or @@ -48,7 +42,7 @@ public interface RemoteSession { Process exec(String commandName, int timeout) throws IOException; /** - * Obtain an {@link FtpChannel} for performing FTP operations over this + * Obtains an {@link FtpChannel} for performing FTP operations over this * {@link RemoteSession}. The default implementation returns {@code null}. * * @return the {@link FtpChannel} @@ -59,7 +53,7 @@ public interface RemoteSession { } /** - * Disconnect the remote session + * Disconnects the remote session. */ void disconnect(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java index ef845f4dce..e216a56ac6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others + * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -27,12 +27,15 @@ import org.eclipse.jgit.util.SystemReader; * Different implementations of the session factory may be used to control * communicating with the end-user as well as reading their personal SSH * configuration settings, such as known hosts and private keys. + * </p> * <p> - * A {@link org.eclipse.jgit.transport.RemoteSession} must be returned to the - * factory that created it. Callers are encouraged to retain the - * SshSessionFactory for the duration of the period they are using the Session. + * A {@link RemoteSession} must be returned to the factory that created it. + * Callers are encouraged to retain the SshSessionFactory for the duration of + * the period they are using the session. + * </p> */ public abstract class SshSessionFactory { + private static SshSessionFactory INSTANCE = loadSshSessionFactory(); private static SshSessionFactory loadSshSessionFactory() { @@ -43,12 +46,13 @@ public abstract class SshSessionFactory { } return null; } - + /** - * Get the currently configured JVM-wide factory. + * Gets the currently configured JVM-wide factory. * <p> - * By default the factory will read from the user's <code>$HOME/.ssh</code> - * and assume OpenSSH compatibility. + * By default the factory will read from the user's {@code $HOME/.ssh} and + * assume OpenSSH compatibility. + * </p> * * @return factory the current factory for this JVM. */ @@ -57,11 +61,11 @@ public abstract class SshSessionFactory { } /** - * Change the JVM-wide factory to a different implementation. + * Changes the JVM-wide factory to a different implementation. * * @param newFactory - * factory for future sessions to be created through. If null the - * default factory will be restored. + * factory for future sessions to be created through; if + * {@code null} the default factory will be restored. */ public static void setInstance(SshSessionFactory newFactory) { if (newFactory != null) { @@ -85,26 +89,23 @@ public abstract class SshSessionFactory { } /** - * Open (or reuse) a session to a host. - * <p> - * A reasonable UserInfo that can interact with the end-user (if necessary) - * is installed on the returned session by this method. - * <p> - * The caller must connect the session by invoking <code>connect()</code> if - * it has not already been connected. + * Opens (or reuses) a session to a host. The returned session is connected + * and authenticated and is ready for further use. * * @param uri - * URI information about the remote host + * URI of the remote host to connect to * @param credentialsProvider - * provider to support authentication, may be null. + * provider to support authentication, may be {@code null} if no + * user input for authentication is needed * @param fs - * the file system abstraction which will be necessary to perform - * certain file system operations. + * the file system abstraction to use for certain file + * operations, such as reading configuration files * @param tms - * Timeout value, in milliseconds. - * @return a session that can contact the remote host. + * connection timeout for creating the session, in milliseconds + * @return a connected and authenticated session for communicating with the + * remote host given by the {@code uri} * @throws org.eclipse.jgit.errors.TransportException - * the session could not be created. + * if the session could not be created */ public abstract RemoteSession getSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms) @@ -120,7 +121,7 @@ public abstract class SshSessionFactory { public abstract String getType(); /** - * Close (or recycle) a session to a host. + * Closes (or recycles) a session to a host. * * @param session * a session previously obtained from this factory's diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java index cc577fa11e..0b38159c09 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -21,6 +21,7 @@ import java.util.Map; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.storage.file.LazyObjectIdSetFile; import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectIdSet; @@ -60,11 +61,19 @@ public class TransferConfig { } /** - * A git configuration variable for which versions of the Git protocol to prefer. - * Used in protocol.version. + * A git configuration variable for which versions of the Git protocol to + * prefer. Used in protocol.version. + * + * @since 5.9 */ - enum ProtocolVersion { + public enum ProtocolVersion { + /** + * Git wire protocol version 0 (the default). + */ V0("0"), //$NON-NLS-1$ + /** + * Git wire protocol version 2. + */ V2("2"); //$NON-NLS-1$ final String name; @@ -73,6 +82,15 @@ public class TransferConfig { this.name = name; } + /** + * Returns version number + * + * @return string version + */ + public String version() { + return name; + } + @Nullable static ProtocolVersion parse(@Nullable String name) { if (name == null) { @@ -177,7 +195,9 @@ public class TransferConfig { "uploadpack", "allowreachablesha1inwant", false); allowFilter = rc.getBoolean( "uploadpack", "allowfilter", false); - protocolVersion = ProtocolVersion.parse(rc.getString("protocol", null, "version")); + protocolVersion = ProtocolVersion.parse(rc + .getString(ConfigConstants.CONFIG_PROTOCOL_SECTION, null, + ConfigConstants.CONFIG_KEY_VERSION)); hideRefs = rc.getStringList("uploadpack", null, "hiderefs"); allowSidebandAll = rc.getBoolean( "uploadpack", "allowsidebandall", false); 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 994af2607c..4c26dd0c40 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -2,7 +2,7 @@ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com> - * Copyright (C) 2012-2013, Robin Rosenberg and others + * Copyright (C) 2012-2020, Robin Rosenberg and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -522,6 +522,17 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { return state.options; } + /** + * Retrieves the {@link Repository} this {@link WorkingTreeIterator} + * operates on. + * + * @return the {@link Repository} + * @since 5.9 + */ + public Repository getRepository() { + return repository; + } + /** {@inheritDoc} */ @Override public int idOffset() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 91574efec4..bf7b753693 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -52,7 +52,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -186,12 +186,18 @@ public abstract class FS { */ public static final class FileStoreAttributes { + /** + * Marker to detect undefined values when reading from the config file. + */ private static final Duration UNDEFINED_DURATION = Duration .ofNanos(Long.MAX_VALUE); /** * Fallback filesystem timestamp resolution. The worst case timestamp * resolution on FAT filesystems is 2 seconds. + * <p> + * Must be at least 1 second. + * </p> */ public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration .ofMillis(2000); @@ -204,6 +210,25 @@ public abstract class FS { public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes( FALLBACK_TIMESTAMP_RESOLUTION); + private static final long ONE_MICROSECOND = TimeUnit.MICROSECONDS + .toNanos(1); + + private static final long ONE_MILLISECOND = TimeUnit.MILLISECONDS + .toNanos(1); + + private static final long ONE_SECOND = TimeUnit.SECONDS.toNanos(1); + + /** + * Minimum file system timestamp resolution granularity to check, in + * nanoseconds. Should be a positive power of ten smaller than + * {@link #ONE_SECOND}. Must be strictly greater than zero, i.e., + * minimum value is 1 nanosecond. + * <p> + * Currently set to 1 microsecond, but could also be lower still. + * </p> + */ + private static final long MINIMUM_RESOLUTION_NANOS = ONE_MICROSECOND; + private static final String JAVA_VERSION_PREFIX = System .getProperty("java.vendor") + '|' //$NON-NLS-1$ + System.getProperty("java.version") + '|'; //$NON-NLS-1$ @@ -235,9 +260,10 @@ public abstract class FS { * @see java.util.concurrent.Executors#newCachedThreadPool() */ private static final Executor FUTURE_RUNNER = new ThreadPoolExecutor(0, - 5, 30L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), + 5, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), runnable -> { - Thread t = new Thread(runnable, "FileStoreAttributeReader-" //$NON-NLS-1$ + Thread t = new Thread(runnable, + "JGit-FileStoreAttributeReader-" //$NON-NLS-1$ + threadNumber.getAndIncrement()); // Make sure these threads don't prevent application/JVM // shutdown. @@ -246,12 +272,34 @@ public abstract class FS { }); /** + * Use a separate executor with at most one thread to synchronize + * writing to the config. We write asynchronously since the config + * itself might be on a different file system, which might otherwise + * lead to locking problems. + * <p> + * Writing the config must not use a daemon thread, otherwise we may + * leave an inconsistent state on disk when the JVM shuts down. Use a + * small keep-alive time to avoid delays on shut-down. + * </p> + */ + private static final Executor SAVE_RUNNER = new ThreadPoolExecutor(0, 1, + 1L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), + runnable -> { + Thread t = new Thread(runnable, + "JGit-FileStoreAttributeWriter-" //$NON-NLS-1$ + + threadNumber.getAndIncrement()); + // Make sure these threads do finish + t.setDaemon(false); + return t; + }); + + /** * Whether FileStore attributes should be determined asynchronously * * @param async * whether FileStore attributes should be determined - * asynchronously. If false access to cached attributes may block - * for some seconds for the first call per FileStore + * asynchronously. If false access to cached attributes may + * block for some seconds for the first call per FileStore * @since 5.6.2 */ public static void setBackground(boolean async) { @@ -294,6 +342,10 @@ public abstract class FS { return cached; } FileStoreAttributes attrs = getFileStoreAttributes(dir); + if (attrs == null) { + // Don't cache, result might be late + return FALLBACK_FILESTORE_ATTRIBUTES; + } attrCacheByPath.put(dir, attrs); return attrs; } catch (SecurityException e) { @@ -367,7 +419,9 @@ public abstract class FS { if (LOG.isDebugEnabled()) { LOG.debug(c.toString()); } - saveToConfig(s, c); + FileStoreAttributes newAttrs = c; + SAVE_RUNNER.execute( + () -> saveToConfig(s, newAttrs)); } attributes = Optional.of(c); } finally { @@ -382,12 +436,16 @@ public abstract class FS { }); // even if measuring in background wait a little - if the result // arrives, it's better than returning the large fallback - Optional<FileStoreAttributes> d = background.get() ? f.get( + boolean runInBackground = background.get(); + Optional<FileStoreAttributes> d = runInBackground ? f.get( 100, TimeUnit.MILLISECONDS) : f.get(); if (d.isPresent()) { return d.get(); + } else if (runInBackground) { + // return null until measurement is finished + return null; } - // return fallback until measurement is finished + // fall through and return fallback } catch (IOException | InterruptedException | ExecutionException | CancellationException e) { LOG.error(e.getMessage(), e); @@ -467,24 +525,21 @@ public abstract class FS { private static Optional<Duration> measureFsTimestampResolution( FileStore s, Path dir) { - LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$ - Thread.currentThread(), s, dir); + if (LOG.isDebugEnabled()) { + LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$ + Thread.currentThread(), s, dir); + } Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ try { Files.createFile(probe); - FileTime t1 = Files.getLastModifiedTime(probe); - FileTime t2 = t1; - Instant t1i = t1.toInstant(); - for (long i = 1; t2.compareTo(t1) <= 0; i += 1 + i / 20) { - Files.setLastModifiedTime(probe, - FileTime.from(t1i.plusNanos(i * 1000))); - t2 = Files.getLastModifiedTime(probe); - } - Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant()); + Duration fsResolution = getFsResolution(s, dir, probe); Duration clockResolution = measureClockResolution(); fsResolution = fsResolution.plus(clockResolution); - LOG.debug("{}: end measure timestamp resolution {} in {}", //$NON-NLS-1$ - Thread.currentThread(), s, dir); + if (LOG.isDebugEnabled()) { + LOG.debug( + "{}: end measure timestamp resolution {} in {}; got {}", //$NON-NLS-1$ + Thread.currentThread(), s, dir, fsResolution); + } return Optional.of(fsResolution); } catch (SecurityException e) { // Log it here; most likely deleteProbe() below will also run @@ -501,6 +556,92 @@ public abstract class FS { return Optional.empty(); } + private static Duration getFsResolution(FileStore s, Path dir, + Path probe) throws IOException { + File probeFile = probe.toFile(); + FileTime t1 = Files.getLastModifiedTime(probe); + Instant t1i = t1.toInstant(); + FileTime t2; + Duration last = FALLBACK_TIMESTAMP_RESOLUTION; + long minScale = MINIMUM_RESOLUTION_NANOS; + long scale = ONE_SECOND; + long high = TimeUnit.MILLISECONDS.toSeconds(last.toMillis()); + long low = 0; + // Try up-front at microsecond and millisecond + long[] tries = { ONE_MICROSECOND, ONE_MILLISECOND }; + for (long interval : tries) { + if (interval >= ONE_MILLISECOND) { + probeFile.setLastModified( + t1i.plusNanos(interval).toEpochMilli()); + } else { + Files.setLastModifiedTime(probe, + FileTime.from(t1i.plusNanos(interval))); + } + t2 = Files.getLastModifiedTime(probe); + if (t2.compareTo(t1) > 0) { + Duration diff = Duration.between(t1i, t2.toInstant()); + if (!diff.isZero() && !diff.isNegative() + && diff.compareTo(last) < 0) { + scale = interval; + high = 1; + last = diff; + break; + } + } else { + // Makes no sense going below + minScale = Math.max(minScale, interval); + } + } + // Binary search loop + while (high > low) { + long mid = (high + low) / 2; + if (mid == 0) { + // Smaller than current scale. Adjust scale. + long newScale = scale / 10; + if (newScale < minScale) { + break; + } + high *= scale / newScale; + low *= scale / newScale; + scale = newScale; + mid = (high + low) / 2; + } + long delta = mid * scale; + if (scale >= ONE_MILLISECOND) { + probeFile.setLastModified( + t1i.plusNanos(delta).toEpochMilli()); + } else { + Files.setLastModifiedTime(probe, + FileTime.from(t1i.plusNanos(delta))); + } + t2 = Files.getLastModifiedTime(probe); + int cmp = t2.compareTo(t1); + if (cmp > 0) { + high = mid; + Duration diff = Duration.between(t1i, t2.toInstant()); + if (diff.isZero() || diff.isNegative()) { + LOG.warn(JGitText.get().logInconsistentFiletimeDiff, + Thread.currentThread(), s, dir, t2, t1, diff, + last); + break; + } else if (diff.compareTo(last) > 0) { + LOG.warn(JGitText.get().logLargerFiletimeDiff, + Thread.currentThread(), s, dir, diff, last); + break; + } + last = diff; + } else if (cmp < 0) { + LOG.warn(JGitText.get().logSmallerFiletime, + Thread.currentThread(), s, dir, t2, t1, last); + break; + } else { + // No discernible difference + low = mid + 1; + } + } + return last; + } + private static Duration measureClockResolution() { Duration clockResolution = Duration.ZERO; for (int i = 0; i < 10; i++) { @@ -1356,7 +1497,7 @@ public abstract class FS { String v; try { v = readPipe(gitExe.getParentFile(), - new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$ + new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$ Charset.defaultCharset().name()); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); @@ -1375,7 +1516,8 @@ public abstract class FS { String w; try { w = readPipe(gitExe.getParentFile(), - new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$ + "--edit" }, //$NON-NLS-1$ Charset.defaultCharset().name(), env); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); 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 c9d2770b18..fb63dc02bb 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 @@ -54,6 +54,8 @@ import org.slf4j.LoggerFactory; public class FS_POSIX extends FS { private static final Logger LOG = LoggerFactory.getLogger(FS_POSIX.class); + private static final String DEFAULT_GIT_LOCATION = "/usr/bin/git"; //$NON-NLS-1$ + private static final int DEFAULT_UMASK = 0022; private volatile int umask = -1; @@ -138,24 +140,46 @@ public class FS_POSIX extends FS { String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$ File gitExe = searchPath(path, "git"); //$NON-NLS-1$ - if (gitExe == null) { - if (SystemReader.getInstance().isMacOS()) { + if (SystemReader.getInstance().isMacOS()) { + if (gitExe == null + || DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) { if (searchPath(path, "bash") != null) { //$NON-NLS-1$ // On MacOSX, PATH is shorter when Eclipse is launched from the // Finder than from a terminal. Therefore try to launch bash as a // login shell and search using that. - String w; try { - w = readPipe(userHome(), + String w = readPipe(userHome(), new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ Charset.defaultCharset().name()); + if (!StringUtils.isEmptyOrNull(w)) { + gitExe = new File(w); + } } catch (CommandFailedException e) { LOG.warn(e.getMessage()); - return null; } - if (!StringUtils.isEmptyOrNull(w)) { - gitExe = new File(w); + } + } + if (gitExe != null + && DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) { + // If we still have the default git exe, it's an XCode wrapper + // that may prompt the user to install the XCode command line + // tools if not already present. Avoid the prompt by returning + // null if no XCode git is there. + try { + String w = readPipe(userHome(), + new String[] { "xcode-select", "-p" }, //$NON-NLS-1$ //$NON-NLS-2$ + Charset.defaultCharset().name()); + if (StringUtils.isEmptyOrNull(w)) { + gitExe = null; + } else { + File realGitExe = new File(new File(w), + DEFAULT_GIT_LOCATION.substring(1)); + if (!realGitExe.exists()) { + gitExe = null; + } } + } catch (CommandFailedException e) { + gitExe = null; } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java index a151cd336f..e29704158d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java @@ -11,6 +11,7 @@ package org.eclipse.jgit.util; import java.io.IOException; import java.text.MessageFormat; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CommandFailedException; @@ -61,11 +62,21 @@ public class SshSupport { CommandFailedException failure = null; @SuppressWarnings("resource") MessageWriter stderr = new MessageWriter(); + @SuppressWarnings("resource") + MessageWriter stdout = new MessageWriter(); String out; - try (MessageWriter stdout = new MessageWriter()) { + try { + long start = System.nanoTime(); session = SshSessionFactory.getInstance().getSession(sshUri, provider, fs, 1000 * timeout); - process = session.exec(command, 0); + int commandTimeout = timeout; + if (timeout > 0) { + commandTimeout = checkTimeout(command, timeout, start); + } + process = session.exec(command, commandTimeout); + if (timeout > 0) { + commandTimeout = checkTimeout(command, timeout, start); + } errorThread = new StreamCopyThread(process.getErrorStream(), stderr.getRawStream()); errorThread.start(); @@ -73,9 +84,15 @@ public class SshSupport { stdout.getRawStream()); outThread.start(); try { - // waitFor with timeout has a bug - JSch' exitValue() throws the - // wrong exception type :( - if (process.waitFor() == 0) { + boolean finished = false; + if (timeout <= 0) { + process.waitFor(); + finished = true; + } else { + finished = process.waitFor(commandTimeout, + TimeUnit.SECONDS); + } + if (finished) { out = stdout.toString(); } else { out = null; // still running after timeout @@ -103,15 +120,26 @@ public class SshSupport { } } if (process != null) { - if (process.exitValue() != 0) { - failure = new CommandFailedException(process.exitValue(), + try { + if (process.exitValue() != 0) { + failure = new CommandFailedException( + process.exitValue(), + MessageFormat.format( + JGitText.get().sshCommandFailed, + command, stderr.toString())); + } + // It was successful after all + out = stdout.toString(); + } catch (IllegalThreadStateException e) { + failure = new CommandFailedException(0, MessageFormat.format( - JGitText.get().sshCommandFailed, command, - stderr.toString())); + JGitText.get().sshCommandTimeout, command, + Integer.valueOf(timeout))); } process.destroy(); } stderr.close(); + stdout.close(); if (session != null) { SshSessionFactory.getInstance().releaseSession(session); } @@ -122,4 +150,17 @@ public class SshSupport { return out; } + private static int checkTimeout(String command, int timeout, long since) + throws CommandFailedException { + long elapsed = System.nanoTime() - since; + int newTimeout = timeout + - (int) TimeUnit.NANOSECONDS.toSeconds(elapsed); + if (newTimeout <= 0) { + // All time used up for connecting the session + throw new CommandFailedException(0, + MessageFormat.format(JGitText.get().sshCommandTimeout, + command, Integer.valueOf(timeout))); + } + return newTimeout; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index bcb8380625..447f417e7e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -17,7 +17,6 @@ import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; @@ -116,11 +115,9 @@ public abstract class SystemReader { .getAbsolutePath(); } try { - Path xdgHomePath = Paths.get(configHomePath); - Files.createDirectories(xdgHomePath); - return xdgHomePath; - } catch (IOException | InvalidPathException e) { - LOG.error(JGitText.get().createXDGConfigHomeFailed, + return Paths.get(configHomePath); + } catch (InvalidPathException e) { + LOG.error(JGitText.get().logXDGConfigHomeInvalid, configHomePath, e); } return null; @@ -130,16 +127,9 @@ public abstract class SystemReader { public FileBasedConfig openJGitConfig(Config parent, FS fs) { Path xdgPath = getXDGConfigHome(fs); if (xdgPath != null) { - Path configPath = null; - try { - configPath = xdgPath.resolve("jgit"); //$NON-NLS-1$ - Files.createDirectories(configPath); - configPath = configPath.resolve(Constants.CONFIG); - return new FileBasedConfig(parent, configPath.toFile(), fs); - } catch (IOException e) { - LOG.error(JGitText.get().createJGitConfigFailed, configPath, - e); - } + Path configPath = xdgPath.resolve("jgit") //$NON-NLS-1$ + .resolve(Constants.CONFIG); + return new FileBasedConfig(parent, configPath.toFile(), fs); } return new FileBasedConfig(parent, new File(fs.userHome(), ".jgitconfig"), fs); //$NON-NLS-1$ 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 8c9b1bf5cc..0e335a9dc4 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 @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, 2013 Marc Strapetz <marc.strapetz@syntevo.com> - * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> and others + * Copyright (C) 2015, 2020 Ivan Motsch <ivan.motsch@bsiag.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -13,26 +13,58 @@ package org.eclipse.jgit.util.io; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Set; import org.eclipse.jgit.diff.RawText; /** * An InputStream that normalizes CRLF to LF. - * - * Existing single CR are not changed to LF, but retained as is. - * - * Optionally, a binary check on the first 8000 bytes is performed and in case - * of binary files, canonicalization is turned off (for the complete file). * <p> - * This is the former EolCanonicalizingInputStream with a new name in order to - * have same naming for all LF / CRLF streams + * Existing single CR are not changed to LF but are retained as is. + * </p> + * <p> + * Optionally, a binary check on the first 8kB is performed and in case of + * binary files, canonicalization is turned off (for the complete file). If + * binary checking determines that the input is CR/LF-delimited text and the + * stream has been created for checkout, canonicalization is also turned off. + * </p> * * @since 4.3 */ public class AutoLFInputStream extends InputStream { + + // This is the former EolCanonicalizingInputStream with a new name in order + // to have same naming for all LF / CRLF streams. + + /** + * Flags for controlling auto-detection of binary vs. text content (for + * text=auto). + * + * @since 5.9 + */ + public enum StreamFlag { + /** + * Check the first 8kB for binary content and switch off + * canonicalization off for the whole file if so. + */ + DETECT_BINARY, + /** + * If {@link #DETECT_BINARY} is set, throw an {@link IsBinaryException} + * if binary content is detected. + */ + ABORT_IF_BINARY, + /** + * If {@link #DETECT_BINARY} is set and content is found to be CR-LF + * delimited text, switch off canonicalization. + */ + FOR_CHECKOUT + } + private final byte[] single = new byte[1]; - private final byte[] buf = new byte[8096]; + private final byte[] buf = new byte[8 * 1024]; private final InputStream in; @@ -40,11 +72,23 @@ public class AutoLFInputStream extends InputStream { private int ptr; + /** + * Set to {@code true} if no CR/LF processing is to be done: if the input is + * binary data, or CR/LF-delimited text and {@link StreamFlag#FOR_CHECKOUT} + * was given. + */ + private boolean passAsIs; + + /** + * Set to {@code true} if the input was detected to be binary data. + */ private boolean isBinary; private boolean detectBinary; - private boolean abortIfBinary; + private final boolean abortIfBinary; + + private final boolean forCheckout; /** * A special exception thrown when {@link AutoLFInputStream} is told to @@ -62,20 +106,64 @@ public class AutoLFInputStream extends InputStream { } /** - * Creates a new InputStream, wrapping the specified stream + * Factory method for creating an {@link AutoLFInputStream} with the + * specified {@link StreamFlag flags}. + * + * @param in + * raw input stream + * @param flags + * {@link StreamFlag}s controlling the stream behavior + * @return a new {@link AutoLFInputStream} + * @since 5.9 + */ + public static AutoLFInputStream create(InputStream in, + StreamFlag... flags) { + if (flags == null) { + return new AutoLFInputStream(in, null); + } + EnumSet<StreamFlag> set = EnumSet.noneOf(StreamFlag.class); + set.addAll(Arrays.asList(flags)); + return new AutoLFInputStream(in, set); + } + + /** + * Creates a new InputStream, wrapping the specified stream. + * + * @param in + * raw input stream + * @param flags + * {@link StreamFlag}s controlling the stream behavior; + * {@code null} is treated as an empty set + * @since 5.9 + */ + public AutoLFInputStream(InputStream in, Set<StreamFlag> flags) { + this.in = in; + this.detectBinary = flags != null + && flags.contains(StreamFlag.DETECT_BINARY); + this.abortIfBinary = flags != null + && flags.contains(StreamFlag.ABORT_IF_BINARY); + this.forCheckout = flags != null + && flags.contains(StreamFlag.FOR_CHECKOUT); + } + + /** + * Creates a new InputStream, wrapping the specified stream. * * @param in * raw input stream * @param detectBinary * whether binaries should be detected * @since 2.0 + * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)} + * instead */ + @Deprecated public AutoLFInputStream(InputStream in, boolean detectBinary) { this(in, detectBinary, false); } /** - * Creates a new InputStream, wrapping the specified stream + * Creates a new InputStream, wrapping the specified stream. * * @param in * raw input stream @@ -84,12 +172,16 @@ public class AutoLFInputStream extends InputStream { * @param abortIfBinary * throw an IOException if the file is binary * @since 3.3 + * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)} + * instead */ + @Deprecated public AutoLFInputStream(InputStream in, boolean detectBinary, boolean abortIfBinary) { this.in = in; this.detectBinary = detectBinary; this.abortIfBinary = abortIfBinary; + this.forCheckout = false; } /** {@inheritDoc} */ @@ -118,7 +210,7 @@ public class AutoLFInputStream extends InputStream { } byte b = buf[ptr++]; - if (isBinary || b != '\r') { + if (passAsIs || b != '\r') { // Logic for binary files ends here bs[i++] = b; continue; @@ -170,9 +262,14 @@ public class AutoLFInputStream extends InputStream { } if (detectBinary) { isBinary = RawText.isBinary(buf, cnt); + passAsIs = isBinary; detectBinary = false; - if (isBinary && abortIfBinary) + if (isBinary && abortIfBinary) { throw new IsBinaryException(); + } + if (!passAsIs && forCheckout) { + passAsIs = RawText.isCrLfText(buf, cnt); + } } ptr = 0; return true; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java index e235aa0ed4..195fdb4213 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java @@ -1,43 +1,12 @@ /* * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> + * Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> and others * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.util.io; @@ -49,11 +18,15 @@ import org.eclipse.jgit.diff.RawText; /** * An OutputStream that reduces CRLF to LF. - * + * <p> * Existing single CR are not changed to LF, but retained as is. - * + * </p> + * <p> * A binary check on the first 8000 bytes is performed and in case of binary - * files, canonicalization is turned off (for the complete file). + * files, canonicalization is turned off (for the complete file). If the binary + * check determines that the input is not binary but text with CR/LF, + * canonicalization is also turned off. + * </p> * * @since 4.3 */ @@ -76,9 +49,7 @@ public class AutoLFOutputStream extends OutputStream { private boolean isBinary; /** - * <p> * Constructor for AutoLFOutputStream. - * </p> * * @param out * an {@link java.io.OutputStream} object. @@ -88,9 +59,7 @@ public class AutoLFOutputStream extends OutputStream { } /** - * <p> * Constructor for AutoLFOutputStream. - * </p> * * @param out * an {@link java.io.OutputStream} object. @@ -123,14 +92,11 @@ public class AutoLFOutputStream extends OutputStream { public void write(byte[] b, int startOff, int startLen) throws IOException { final int overflow = buffer(b, startOff, startLen); - if (overflow < 0) { + if (overflow <= 0) { return; } final int off = startOff + startLen - overflow; final int len = overflow; - if (len == 0) { - return; - } int lastw = off; if (isBinary) { out.write(b, off, len); @@ -190,6 +156,9 @@ public class AutoLFOutputStream extends OutputStream { private void decideMode() throws IOException { if (detectBinary) { isBinary = RawText.isBinary(binbuf, binbufcnt); + if (!isBinary) { + isBinary = RawText.isCrLfText(binbuf, binbufcnt); + } detectBinary = false; } int cachedLen = binbufcnt; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java index c33c869b64..88ee2aee88 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> and others + * Copyright (C) 2015, 2020 Ivan Motsch <ivan.motsch@bsiag.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -12,12 +12,14 @@ package org.eclipse.jgit.util.io; import java.io.InputStream; import java.io.OutputStream; +import java.util.EnumSet; import org.eclipse.jgit.attributes.Attributes; import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.util.SystemReader; +import org.eclipse.jgit.util.io.AutoLFInputStream.StreamFlag; /** * Utility used to create input and output stream wrappers for @@ -71,7 +73,7 @@ public final class EolStreamTypeUtil { /** * Wrap the input stream depending on - * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}. * * @param in * original stream @@ -82,15 +84,38 @@ public final class EolStreamTypeUtil { */ public static InputStream wrapInputStream(InputStream in, EolStreamType conversion) { + return wrapInputStream(in, conversion, false); + } + + /** + * Wrap the input stream depending on + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}. + * + * @param in + * original stream + * @param conversion + * to be performed + * @param forCheckout + * whether the stream is for checking out from the repository + * @return the converted stream depending on + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + * @since 5.9 + */ + public static InputStream wrapInputStream(InputStream in, + EolStreamType conversion, boolean forCheckout) { switch (conversion) { case TEXT_CRLF: return new AutoCRLFInputStream(in, false); case TEXT_LF: - return new AutoLFInputStream(in, false); + return AutoLFInputStream.create(in); case AUTO_CRLF: return new AutoCRLFInputStream(in, true); case AUTO_LF: - return new AutoLFInputStream(in, true); + EnumSet<StreamFlag> flags = forCheckout + ? EnumSet.of(StreamFlag.DETECT_BINARY, + StreamFlag.FOR_CHECKOUT) + : EnumSet.of(StreamFlag.DETECT_BINARY); + return new AutoLFInputStream(in, flags); default: return in; } @@ -98,7 +123,7 @@ public final class EolStreamTypeUtil { /** * Wrap the output stream depending on - * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType} + * {@link org.eclipse.jgit.lib.CoreConfig.EolStreamType}. * * @param out * original stream |