diff options
208 files changed, 7317 insertions, 1857 deletions
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF index 84e092767c..83051f9c87 100644 --- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF @@ -3,14 +3,14 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.ant.test -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: org.apache.tools.ant, - org.eclipse.jgit.ant.tasks;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.junit;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)", + org.eclipse.jgit.ant.tasks;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)", org.hamcrest;version="[1.1.0,2.0.0)", org.junit;version="[4.0.0,5.0.0)" diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml index b213b5982f..67e6c38a88 100644 --- a/org.eclipse.jgit.ant.test/pom.xml +++ b/org.eclipse.jgit.ant.test/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.ant.test</artifactId> diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF index adea04ec7a..5ff9fd511c 100644 --- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF @@ -2,11 +2,11 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.jgit.ant -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: org.apache.tools.ant, - org.eclipse.jgit.storage.file;version="[4.0.3,4.1.0)" + org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)" Bundle-Localization: plugin Bundle-Vendor: %Provider-Name -Export-Package: org.eclipse.jgit.ant.tasks;version="4.0.3"; +Export-Package: org.eclipse.jgit.ant.tasks;version="4.1.2"; uses:="org.apache.tools.ant.types,org.apache.tools.ant" diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml index 580beaddb6..f40e0f7361 100644 --- a/org.eclipse.jgit.ant/pom.xml +++ b/org.eclipse.jgit.ant/pom.xml @@ -48,7 +48,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.ant</artifactId> diff --git a/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java b/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java index c76ae2af8c..b9a868826e 100644 --- a/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java +++ b/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitAddTask.java @@ -117,10 +117,10 @@ public class GitAddTask extends Task { } AddCommand gitAdd; - try { - Repository repo = new FileRepositoryBuilder().readEnvironment() - .findGitDir(src).build(); - gitAdd = new Git(repo).add(); + try (Repository repo = new FileRepositoryBuilder().readEnvironment() + .findGitDir(src).build(); + Git git = new Git(repo);) { + gitAdd = git.add(); } catch (IOException e) { throw new BuildException("Could not access repository " + src, e); } diff --git a/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java b/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java index 14c4bc5700..9962472c9e 100644 --- a/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java +++ b/org.eclipse.jgit.ant/src/org/eclipse/jgit/ant/tasks/GitCheckoutTask.java @@ -105,10 +105,10 @@ public class GitCheckoutTask extends Task { @Override public void execute() throws BuildException { CheckoutCommand checkout; - try { - Repository repo = new FileRepositoryBuilder().readEnvironment() - .findGitDir(src).build(); - checkout = new Git(repo).checkout(); + try (Repository repo = new FileRepositoryBuilder().readEnvironment() + .findGitDir(src).build(); + Git git = new Git(repo)) { + checkout = git.checkout(); } catch (IOException e) { throw new BuildException("Could not access repository " + src, e); } diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs index ff39d16ab7..4e28e0b26b 100644 --- a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs @@ -1,10 +1,10 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 @@ -67,11 +67,11 @@ org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullReference=error org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error -org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=error org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF index 7db8615ba8..3787f8675e 100644 --- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.archive -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Vendor: %provider_name Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 @@ -12,15 +12,16 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.4,2.0)", org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)", org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)", org.apache.commons.compress.compressors.xz;version="[1.4,2.0)", - org.eclipse.jgit.api;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.nls;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)", + org.eclipse.jgit.api;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)", org.osgi.framework;version="[1.3.0,2.0.0)" Bundle-ActivationPolicy: lazy Bundle-Activator: org.eclipse.jgit.archive.FormatActivator -Export-Package: org.eclipse.jgit.archive;version="4.0.3"; +Export-Package: org.eclipse.jgit.archive;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.api, org.apache.commons.compress.archivers, org.osgi.framework" +Require-Bundle: org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF index 06084e2aa9..d1f674ddb2 100644 --- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF @@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.jgit.archive - Sources Bundle-SymbolicName: org.eclipse.jgit.archive.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.0.3.201509231615-r -Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.0.3.201509231615-r";roots="." +Bundle-Version: 4.1.2.201602141800-r +Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.1.2.201602141800-r";roots="." diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml index 5bb3eda339..377d8128c5 100644 --- a/org.eclipse.jgit.archive/pom.xml +++ b/org.eclipse.jgit.archive/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.archive</artifactId> diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF index d30a17baff..182262f5e1 100644 --- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF @@ -2,10 +2,11 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.jgit.http.apache -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-Localization: plugin Bundle-Vendor: %Provider-Name +Bundle-ActivationPolicy: lazy Import-Package: org.apache.http;version="[4.1.0,5.0.0)", org.apache.http.client;version="[4.1.0,5.0.0)", org.apache.http.client.methods;version="[4.1.0,5.0.0)", @@ -18,12 +19,13 @@ Import-Package: org.apache.http;version="[4.1.0,5.0.0)", org.apache.http.impl.client;version="[4.1.0,5.0.0)", org.apache.http.impl.client.cache;version="[4.1.0,5.0.0)", org.apache.http.params;version="[4.1.0,5.0.0)", - org.eclipse.jgit.nls;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.http;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)" -Export-Package: org.eclipse.jgit.transport.http.apache;version="4.0.3"; + org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)" +Export-Package: org.eclipse.jgit.transport.http.apache;version="4.1.2"; uses:="org.eclipse.jgit.transport.http, javax.net.ssl, org.apache.http.client, org.apache.http.client.methods, - org.apache.http" + org.apache.http", + org.eclipse.jgit.transport.http.apache.internal;x-internal:=true diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml index 37f346216a..7510088dc1 100644 --- a/org.eclipse.jgit.http.apache/pom.xml +++ b/org.eclipse.jgit.http.apache/pom.xml @@ -48,7 +48,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.http.apache</artifactId> diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF index 135525f8cb..b66606c6d4 100644 --- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF @@ -2,13 +2,13 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.http.server -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Localization: plugin Bundle-Vendor: %provider_name -Export-Package: org.eclipse.jgit.http.server;version="4.0.3", - org.eclipse.jgit.http.server.glue;version="4.0.3"; +Export-Package: org.eclipse.jgit.http.server;version="4.1.2", + org.eclipse.jgit.http.server.glue;version="4.1.2"; uses:="javax.servlet,javax.servlet.http", - org.eclipse.jgit.http.server.resolver;version="4.0.3"; + org.eclipse.jgit.http.server.resolver;version="4.1.2"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.lib, org.eclipse.jgit.transport, @@ -17,12 +17,12 @@ Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: javax.servlet;version="[2.5.0,3.2.0)", javax.servlet.http;version="[2.5.0,3.2.0)", - org.eclipse.jgit.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.dfs;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.nls;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.resolver;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)" + org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.dfs;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)" diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml index 7e1b8ef3e7..ea60b00ab2 100644 --- a/org.eclipse.jgit.http.server/pom.xml +++ b/org.eclipse.jgit.http.server/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.http.server</artifactId> diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java index b2250e3ef3..d33362b4b4 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java @@ -80,14 +80,16 @@ class AsIsFileFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse res = (HttpServletResponse) response; try { final Repository db = getRepository(request); - asIs.access((HttpServletRequest) request, db); + asIs.access(req, db); chain.doFilter(request, response); } catch (ServiceNotAuthorizedException e) { - ((HttpServletResponse) response).sendError(SC_UNAUTHORIZED); + res.sendError(SC_UNAUTHORIZED, e.getMessage()); } catch (ServiceNotEnabledException e) { - ((HttpServletResponse) response).sendError(SC_FORBIDDEN); + res.sendError(SC_FORBIDDEN, e.getMessage()); } } } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java index 030287b5cf..c88670ec97 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java @@ -76,6 +76,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.UnpackException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.InternalHttpServerGlue; @@ -137,11 +138,10 @@ class ReceivePackServlet extends HttpServlet { try { rp = receivePackFactory.create(req, getRepository(req)); } catch (ServiceNotAuthorizedException e) { - rsp.sendError(SC_UNAUTHORIZED); + rsp.sendError(SC_UNAUTHORIZED, e.getMessage()); return; - } catch (ServiceNotEnabledException e) { - sendError(req, rsp, SC_FORBIDDEN); + sendError(req, rsp, SC_FORBIDDEN, e.getMessage()); return; } @@ -201,7 +201,7 @@ class ReceivePackServlet extends HttpServlet { consumeRequestBody(req); out.close(); - } catch (UnpackException e) { + } catch (UnpackException | PackProtocolException e) { // This should be already reported to the client. log(rp.getRepository(), e.getCause()); consumeRequestBody(req); diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java index 58404020cd..a021c1ff5e 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java @@ -137,10 +137,10 @@ public class RepositoryFilter implements Filter { sendError(req, res, SC_NOT_FOUND); return; } catch (ServiceNotEnabledException e) { - sendError(req, res, SC_FORBIDDEN); + sendError(req, res, SC_FORBIDDEN, e.getMessage()); return; } catch (ServiceNotAuthorizedException e) { - res.sendError(SC_UNAUTHORIZED); + res.sendError(SC_UNAUTHORIZED, e.getMessage()); return; } catch (ServiceMayNotContinueException e) { sendError(req, res, SC_FORBIDDEN, e.getMessage()); diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java index 51332194b8..7d4f21b5c4 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java @@ -98,7 +98,7 @@ abstract class SmartServiceInfoRefs implements Filter { try { begin(req, db); } catch (ServiceNotAuthorizedException e) { - res.sendError(SC_UNAUTHORIZED); + res.sendError(SC_UNAUTHORIZED, e.getMessage()); return; } catch (ServiceNotEnabledException e) { sendError(req, res, SC_FORBIDDEN, e.getMessage()); @@ -132,11 +132,9 @@ abstract class SmartServiceInfoRefs implements Filter { advertise(req, new PacketLineOutRefAdvertiser(out)); buf.close(); } catch (ServiceNotAuthorizedException e) { - res.sendError(SC_UNAUTHORIZED); - + res.sendError(SC_UNAUTHORIZED, e.getMessage()); } catch (ServiceNotEnabledException e) { - sendError(req, res, SC_FORBIDDEN); - + sendError(req, res, SC_FORBIDDEN, e.getMessage()); } catch (ServiceMayNotContinueException e) { if (e.isOutput()) buf.close(); diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java index 44d4b1bcfa..8c27b712ba 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java @@ -137,11 +137,10 @@ class UploadPackServlet extends HttpServlet { try { rp = uploadPackFactory.create(req, getRepository(req)); } catch (ServiceNotAuthorizedException e) { - rsp.sendError(SC_UNAUTHORIZED); + rsp.sendError(SC_UNAUTHORIZED, e.getMessage()); return; - } catch (ServiceNotEnabledException e) { - sendError(req, rsp, SC_FORBIDDEN); + sendError(req, rsp, SC_FORBIDDEN, e.getMessage()); return; } diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF index ad965a9cf6..a5bf31a082 100644 --- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.http.test -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Vendor: %provider_name Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 @@ -22,23 +22,23 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)", org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.thread;version="[9.0.0,10.0.0)", - org.eclipse.jgit.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.http.server;version="[4.0.3,4.1.0)", - org.eclipse.jgit.http.server.glue;version="[4.0.3,4.1.0)", - org.eclipse.jgit.http.server.resolver;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.junit;version="[4.0.3,4.1.0)", - org.eclipse.jgit.junit.http;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.nls;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.http;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.http.apache;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.resolver;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)", + org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.http.server;version="[4.1.2,4.2.0)", + org.eclipse.jgit.http.server.glue;version="[4.1.2,4.2.0)", + org.eclipse.jgit.http.server.resolver;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", + org.eclipse.jgit.junit.http;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.http.apache;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)", org.hamcrest.core;version="[1.1.0,2.0.0)", org.junit;version="[4.0.0,5.0.0)", org.junit.runner;version="[4.0.0,5.0.0)", diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml index 5631230968..17128ed700 100644 --- a/org.eclipse.jgit.http.test/pom.xml +++ b/org.eclipse.jgit.http.test/pom.xml @@ -51,7 +51,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.http.test</artifactId> diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF index c63e16c42e..8a5ee6caa3 100644 --- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.junit.http -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy @@ -20,13 +20,23 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)", org.eclipse.jetty.util.component;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)", - org.eclipse.jgit.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.http.server;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.junit;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.resolver;version="[4.0.3,4.1.0)", + org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.http.server;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", org.junit;version="[4.0.0,5.0.0)" -Export-Package: org.eclipse.jgit.junit.http;version="4.0.3" +Export-Package: org.eclipse.jgit.junit.http;version="4.1.2"; + uses:="org.eclipse.jgit.transport, + org.eclipse.jgit.junit, + javax.servlet.http, + org.eclipse.jgit.lib, + org.eclipse.jgit.revwalk, + org.eclipse.jetty.server.handler, + javax.servlet, + org.eclipse.jetty.server, + org.eclipse.jetty.util.log, + org.eclipse.jetty.servlet" diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml index 38e2594e56..d25ae009e4 100644 --- a/org.eclipse.jgit.junit.http/pom.xml +++ b/org.eclipse.jgit.junit.http/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.junit.http</artifactId> diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF index 2ca7e3a137..ea32b1b4b9 100644 --- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF @@ -2,24 +2,32 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.junit -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Import-Package: org.eclipse.jgit.api;version="[4.0.3,4.1.0)", - org.eclipse.jgit.api.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.dircache;version="[4.0.3,4.1.0)", - org.eclipse.jgit.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.pack;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.merge;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.treewalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.treewalk.filter;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util.io;version="[4.0.3,4.1.0)", +Import-Package: org.eclipse.jgit.api;version="[4.1.2,4.2.0)", + org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", + org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.merge;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", org.junit;version="[4.0.0,5.0.0)" -Export-Package: org.eclipse.jgit.junit;version="4.0.3" +Export-Package: org.eclipse.jgit.junit;version="4.1.2"; + uses:="org.eclipse.jgit.dircache, + org.eclipse.jgit.lib, + org.eclipse.jgit.revwalk, + org.eclipse.jgit.internal.storage.file, + org.eclipse.jgit.treewalk, + org.eclipse.jgit.util, + org.eclipse.jgit.storage.file, + org.eclipse.jgit.api" diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml index 25f88f8139..d3616a714c 100644 --- a/org.eclipse.jgit.junit/pom.xml +++ b/org.eclipse.jgit.junit/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.junit</artifactId> diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index b98db7d187..b5348f9980 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -50,18 +50,13 @@ import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.internal.storage.file.FileRepository; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryCache; +import org.eclipse.jgit.lib.*; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.util.FS; @@ -229,6 +224,102 @@ public abstract class LocalDiskRepositoryTestCase { System.err.println(msg); } + public static final int MOD_TIME = 1; + + public static final int SMUDGE = 2; + + public static final int LENGTH = 4; + + public static final int CONTENT_ID = 8; + + public static final int CONTENT = 16; + + public static final int ASSUME_UNCHANGED = 32; + + /** + * Represent the state of the index in one String. This representation is + * useful when writing tests which do assertions on the state of the index. + * By default information about path, mode, stage (if different from 0) is + * included. A bitmask controls which additional info about + * modificationTimes, smudge state and length is included. + * <p> + * The format of the returned string is described with this BNF: + * + * <pre> + * result = ( "[" path mode stage? time? smudge? length? sha1? content? "]" )* . + * mode = ", mode:" number . + * stage = ", stage:" number . + * time = ", time:t" timestamp-index . + * smudge = "" | ", smudged" . + * length = ", length:" number . + * sha1 = ", sha1:" hex-sha1 . + * content = ", content:" blob-data . + * </pre> + * + * 'stage' is only presented when the stage is different from 0. All + * reported time stamps are mapped to strings like "t0", "t1", ... "tn". The + * smallest reported time-stamp will be called "t0". This allows to write + * assertions against the string although the concrete value of the time + * stamps is unknown. + * + * @param repo + * the repository the index state should be determined for + * + * @param includedOptions + * a bitmask constructed out of the constants {@link #MOD_TIME}, + * {@link #SMUDGE}, {@link #LENGTH}, {@link #CONTENT_ID} and + * {@link #CONTENT} controlling which info is present in the + * resulting string. + * @return a string encoding the index state + * @throws IllegalStateException + * @throws IOException + */ + public static String indexState(Repository repo, int includedOptions) + throws IllegalStateException, IOException { + DirCache dc = repo.readDirCache(); + StringBuilder sb = new StringBuilder(); + TreeSet<Long> timeStamps = null; + + // iterate once over the dircache just to collect all time stamps + if (0 != (includedOptions & MOD_TIME)) { + timeStamps = new TreeSet<Long>(); + for (int i=0; i<dc.getEntryCount(); ++i) + timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); + } + + // iterate again, now produce the result string + for (int i=0; i<dc.getEntryCount(); ++i) { + DirCacheEntry entry = dc.getEntry(i); + sb.append("["+entry.getPathString()+", mode:" + entry.getFileMode()); + int stage = entry.getStage(); + if (stage != 0) + sb.append(", stage:" + stage); + if (0 != (includedOptions & MOD_TIME)) { + sb.append(", time:t"+ + timeStamps.headSet(Long.valueOf(entry.getLastModified())).size()); + } + if (0 != (includedOptions & SMUDGE)) + if (entry.isSmudged()) + sb.append(", smudged"); + if (0 != (includedOptions & LENGTH)) + sb.append(", length:" + + Integer.toString(entry.getLength())); + if (0 != (includedOptions & CONTENT_ID)) + sb.append(", sha1:" + ObjectId.toString(entry.getObjectId())); + if (0 != (includedOptions & CONTENT)) { + sb.append(", content:" + + new String(repo.open(entry.getObjectId(), + Constants.OBJ_BLOB).getCachedBytes(), "UTF-8")); + } + if (0 != (includedOptions & ASSUME_UNCHANGED)) + sb.append(", assume-unchanged:" + + Boolean.toString(entry.isAssumeValid())); + sb.append("]"); + } + return sb.toString(); + } + + /** * Creates a new empty bare repository. * diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index 65551d6579..d24dd44fff 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java @@ -47,6 +47,7 @@ package org.eclipse.jgit.junit; import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.HashMap; @@ -170,6 +171,7 @@ public class MockSystemReader extends SystemReader { * Assign some properties for the currently executing platform */ public void setCurrentPlatform() { + resetOsNames(); setProperty("os.name", System.getProperty("os.name")); setProperty("file.separator", System.getProperty("file.separator")); setProperty("path.separator", System.getProperty("path.separator")); @@ -180,6 +182,7 @@ public class MockSystemReader extends SystemReader { * Emulate Windows */ public void setWindows() { + resetOsNames(); setProperty("os.name", "Windows"); setProperty("file.separator", "\\"); setProperty("path.separator", ";"); @@ -191,10 +194,25 @@ public class MockSystemReader extends SystemReader { * Emulate Unix */ public void setUnix() { + resetOsNames(); setProperty("os.name", "*nix"); // Essentially anything but Windows setProperty("file.separator", "/"); setProperty("path.separator", ":"); setProperty("line.separator", "\n"); setPlatformChecker(); } + + private void resetOsNames() { + Field field; + try { + field = SystemReader.class.getDeclaredField("isWindows"); + field.setAccessible(true); + field.set(null, null); + field = SystemReader.class.getDeclaredField("isMacOS"); + field.setAccessible(true); + field.set(null, null); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java index 83148d0009..ac4539a848 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java @@ -56,11 +56,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.Map; -import java.util.TreeSet; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -154,101 +152,6 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { trash = db.getWorkTree(); } - public static final int MOD_TIME = 1; - - public static final int SMUDGE = 2; - - public static final int LENGTH = 4; - - public static final int CONTENT_ID = 8; - - public static final int CONTENT = 16; - - public static final int ASSUME_UNCHANGED = 32; - - /** - * Represent the state of the index in one String. This representation is - * useful when writing tests which do assertions on the state of the index. - * By default information about path, mode, stage (if different from 0) is - * included. A bitmask controls which additional info about - * modificationTimes, smudge state and length is included. - * <p> - * The format of the returned string is described with this BNF: - * - * <pre> - * result = ( "[" path mode stage? time? smudge? length? sha1? content? "]" )* . - * mode = ", mode:" number . - * stage = ", stage:" number . - * time = ", time:t" timestamp-index . - * smudge = "" | ", smudged" . - * length = ", length:" number . - * sha1 = ", sha1:" hex-sha1 . - * content = ", content:" blob-data . - * </pre> - * - * 'stage' is only presented when the stage is different from 0. All - * reported time stamps are mapped to strings like "t0", "t1", ... "tn". The - * smallest reported time-stamp will be called "t0". This allows to write - * assertions against the string although the concrete value of the time - * stamps is unknown. - * - * @param repo - * the repository the index state should be determined for - * - * @param includedOptions - * a bitmask constructed out of the constants {@link #MOD_TIME}, - * {@link #SMUDGE}, {@link #LENGTH}, {@link #CONTENT_ID} and - * {@link #CONTENT} controlling which info is present in the - * resulting string. - * @return a string encoding the index state - * @throws IllegalStateException - * @throws IOException - */ - public String indexState(Repository repo, int includedOptions) - throws IllegalStateException, IOException { - DirCache dc = repo.readDirCache(); - StringBuilder sb = new StringBuilder(); - TreeSet<Long> timeStamps = null; - - // iterate once over the dircache just to collect all time stamps - if (0 != (includedOptions & MOD_TIME)) { - timeStamps = new TreeSet<Long>(); - for (int i=0; i<dc.getEntryCount(); ++i) - timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); - } - - // iterate again, now produce the result string - for (int i=0; i<dc.getEntryCount(); ++i) { - DirCacheEntry entry = dc.getEntry(i); - sb.append("["+entry.getPathString()+", mode:" + entry.getFileMode()); - int stage = entry.getStage(); - if (stage != 0) - sb.append(", stage:" + stage); - if (0 != (includedOptions & MOD_TIME)) { - sb.append(", time:t"+ - timeStamps.headSet(Long.valueOf(entry.getLastModified())).size()); - } - if (0 != (includedOptions & SMUDGE)) - if (entry.isSmudged()) - sb.append(", smudged"); - if (0 != (includedOptions & LENGTH)) - sb.append(", length:" - + Integer.toString(entry.getLength())); - if (0 != (includedOptions & CONTENT_ID)) - sb.append(", sha1:" + ObjectId.toString(entry.getObjectId())); - if (0 != (includedOptions & CONTENT)) { - sb.append(", content:" - + new String(db.open(entry.getObjectId(), - Constants.OBJ_BLOB).getCachedBytes(), "UTF-8")); - } - if (0 != (includedOptions & ASSUME_UNCHANGED)) - sb.append(", assume-unchanged:" - + Boolean.toString(entry.isAssumeValid())); - sb.append("]"); - } - return sb.toString(); - } - /** * Represent the state of the index in one String. This representation is * useful when writing tests which do assertions on the state of the index. diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml index 59c6a2e668..09b59b0c73 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit" label="%featureName" - version="4.0.3.201509231615-r" + version="4.1.2.201602141800-r" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml index ec7fd40884..ce0f9b940a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml index 2ec128af81..31295cf35d 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.http.apache" label="%featureName" - version="4.0.3.201509231615-r" + version="4.1.2.201602141800-r" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml index 313d526574..350de3c9a7 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml index 52dc5f5da0..cb9fbf36c9 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.junit" label="%featureName" - version="4.0.3.201509231615-r" + version="4.1.2.201602141800-r" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml index ac6d5ba263..cb76911b55 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml index f1b8cea022..e1dcfcd310 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.pgm" label="%featureName" - version="4.0.3.201509231615-r" + version="4.1.2.201602141800-r" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> @@ -27,7 +27,7 @@ version="0.0.0"/> <requires> - <import feature="org.eclipse.jgit" version="4.0.0" match="equivalent"/> + <import feature="org.eclipse.jgit" version="4.1.0" match="equivalent"/> </requires> <plugin diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml index 0dedec3892..9076053ebb 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml index 381178b261..bbd0aa0b8a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.pgm.source" label="%featureName" - version="4.0.3.201509231615-r" + version="4.1.2.201602141800-r" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml index 551835af5d..0e64da4234 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml index 135f9ecea6..88ad03242c 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.repository</artifactId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml index b10c53fac7..17030ff48b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.source" label="%featureName" - version="4.0.3.201509231615-r" + version="4.1.2.201602141800-r" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml index e6d565d10a..aa7128e138 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF index 3464b300e3..38732b581d 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF @@ -2,4 +2,4 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: JGit Target Platform Bundle Bundle-SymbolicName: org.eclipse.jgit.target -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target index decc554bbb..e8670e7cfb 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target @@ -1,26 +1,26 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform --> -<target name="jgit-4.3" sequenceNumber="1436830977"> +<target name="jgit-4.3" sequenceNumber="1440024094"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> - <unit id="org.eclipse.jetty.client" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.client.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.continuation" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.continuation.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.http" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.http.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.io" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.io.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.security" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.security.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.server" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.server.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.servlet" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.servlet.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.util" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.util.source" version="9.2.10.v20150310"/> - <repository id="jetty-9.2.10" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.10.v20150310/"/> + <unit id="org.eclipse.jetty.client" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.client.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.continuation" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.continuation.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.http" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.http.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.io" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.io.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.security" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.security.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.server" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.server.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.servlet" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.servlet.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.util" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.util.source" version="9.2.13.v20150730"/> + <repository id="jetty-9.2.13" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.13.v20150730/"/> </location> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.apache.ant" version="1.9.2.v201404171502"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd index f038aa0d42..062e930640 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.tpd @@ -1,6 +1,6 @@ target "jgit-4.3" with source configurePhase -include "projects/jetty-9.2.10.tpd" +include "projects/jetty-9.2.13.tpd" include "orbit/R20150124073747-Luna-SR2.tpd" location "http://download.eclipse.org/releases/kepler/" { diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target index c7172fe652..0b85d75b02 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target @@ -1,26 +1,26 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform --> -<target name="jgit-4.4" sequenceNumber="1436830965"> +<target name="jgit-4.4" sequenceNumber="1440024079"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> - <unit id="org.eclipse.jetty.client" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.client.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.continuation" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.continuation.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.http" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.http.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.io" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.io.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.security" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.security.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.server" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.server.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.servlet" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.servlet.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.util" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.util.source" version="9.2.10.v20150310"/> - <repository id="jetty-9.2.10" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.10.v20150310/"/> + <unit id="org.eclipse.jetty.client" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.client.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.continuation" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.continuation.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.http" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.http.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.io" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.io.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.security" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.security.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.server" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.server.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.servlet" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.servlet.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.util" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.util.source" version="9.2.13.v20150730"/> + <repository id="jetty-9.2.13" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.13.v20150730/"/> </location> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.apache.ant" version="1.9.2.v201404171502"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd index 0760bcdff5..9b9558fbb7 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.tpd @@ -1,6 +1,6 @@ target "jgit-4.4" with source configurePhase -include "projects/jetty-9.2.10.tpd" +include "projects/jetty-9.2.13.tpd" include "orbit/R20150124073747-Luna-SR2.tpd" location "http://download.eclipse.org/releases/luna/" { diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target index 030bbc2c41..20674827b1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target @@ -1,26 +1,26 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <?pde?> <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform --> -<target name="jgit-4.5" sequenceNumber="1441320200"> +<target name="jgit-4.5" sequenceNumber="1440022750"> <locations> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> - <unit id="org.eclipse.jetty.client" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.client.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.continuation" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.continuation.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.http" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.http.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.io" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.io.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.security" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.security.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.server" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.server.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.servlet" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.servlet.source" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.util" version="9.2.10.v20150310"/> - <unit id="org.eclipse.jetty.util.source" version="9.2.10.v20150310"/> - <repository id="jetty-9.2.10" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.10.v20150310/"/> + <unit id="org.eclipse.jetty.client" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.client.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.continuation" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.continuation.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.http" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.http.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.io" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.io.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.security" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.security.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.server" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.server.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.servlet" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.servlet.source" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.util" version="9.2.13.v20150730"/> + <unit id="org.eclipse.jetty.util.source" version="9.2.13.v20150730"/> + <repository id="jetty-9.2.13" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.13.v20150730/"/> </location> <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit"> <unit id="org.apache.ant" version="1.9.4.v201504302020"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd index 93ffef36c2..cdf24b551c 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd @@ -1,6 +1,6 @@ target "jgit-4.5" with source configurePhase -include "projects/jetty-9.2.10.tpd" +include "projects/jetty-9.2.13.tpd" include "orbit/R20150821153341-Mars.tpd" location "http://download.eclipse.org/releases/mars/" { diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml index 278bca03d9..9b565ecdde 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml @@ -49,7 +49,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.target</artifactId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.10.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.10.tpd deleted file mode 100644 index 5a7ee2e2f1..0000000000 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.10.tpd +++ /dev/null @@ -1,20 +0,0 @@ -target "jetty-9.2.10" with source configurePhase - -location jetty-9.2.10 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.10.v20150310/" { - org.eclipse.jetty.client [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.client.source [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.continuation [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.continuation.source [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.http [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.http.source [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.io [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.io.source [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.security [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.security.source [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.server [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.server.source [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.servlet [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.servlet.source [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.util [9.2.10.v20150310,9.2.10.v20150310] - org.eclipse.jetty.util.source [9.2.10.v20150310,9.2.10.v20150310] -} diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.13.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.13.tpd new file mode 100644 index 0000000000..289a73d2ae --- /dev/null +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.2.13.tpd @@ -0,0 +1,20 @@ +target "jetty-9.2.13" with source configurePhase + +location jetty-9.2.13 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.2.13.v20150730/" { + org.eclipse.jetty.client [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.client.source [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.continuation [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.continuation.source [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.http [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.http.source [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.io [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.io.source [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.security [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.security.source [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.server [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.server.source [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.servlet [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.servlet.source [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.util [9.2.13.v20150730,9.2.13.v20150730] + org.eclipse.jetty.util.source [9.2.13.v20150730,9.2.13.v20150730] +} diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml index 2a3fcb24bf..3b5b7d8224 100644 --- a/org.eclipse.jgit.packaging/pom.xml +++ b/org.eclipse.jgit.packaging/pom.xml @@ -53,7 +53,7 @@ <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> <packaging>pom</packaging> <name>JGit Tycho Parent</name> diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF index 8d6186338f..1693bee1c4 100644 --- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF @@ -2,27 +2,27 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.pgm.test -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Vendor: %provider_name Bundle-Localization: plugin Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Import-Package: org.eclipse.jgit.api;version="[4.0.3,4.1.0)", - org.eclipse.jgit.api.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.diff;version="[4.0.3,4.1.0)", - org.eclipse.jgit.dircache;version="[4.0.3,4.1.0)", - org.eclipse.jgit.junit;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.merge;version="[4.0.3,4.1.0)", - org.eclipse.jgit.pgm;version="[4.0.3,4.1.0)", - org.eclipse.jgit.pgm.internal;version="[4.0.3,4.1.0)", - org.eclipse.jgit.pgm.opt;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport;version="[4.0.3,4.1.0)", - org.eclipse.jgit.treewalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util.io;version="[4.0.3,4.1.0)", +Import-Package: org.eclipse.jgit.api;version="[4.1.2,4.2.0)", + org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.diff;version="[4.1.2,4.2.0)", + org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", + org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.merge;version="[4.1.2,4.2.0)", + org.eclipse.jgit.pgm;version="[4.1.2,4.2.0)", + org.eclipse.jgit.pgm.internal;version="[4.1.2,4.2.0)", + org.eclipse.jgit.pgm.opt;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", + org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", org.junit;version="[4.4.0,5.0.0)", org.kohsuke.args4j;version="[2.0.12,2.1.0)" diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml index c570b1b4a7..cca2e92c60 100644 --- a/org.eclipse.jgit.pgm.test/pom.xml +++ b/org.eclipse.jgit.pgm.test/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.pgm.test</artifactId> diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java index cef9b9e1a6..387eb2bbb4 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.io.File; @@ -59,7 +60,6 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FileUtils; -import org.junit.Assert; import org.junit.Test; public class CheckoutTest extends CLIRepositoryTestCase { @@ -68,7 +68,8 @@ public class CheckoutTest extends CLIRepositoryTestCase { public void testCheckoutSelf() throws Exception { new Git(db).commit().setMessage("initial commit").call(); - assertEquals("Already on 'master'", execute("git checkout master")); + assertStringArrayEquals("Already on 'master'", + execute("git checkout master")); } @Test @@ -76,20 +77,21 @@ public class CheckoutTest extends CLIRepositoryTestCase { new Git(db).commit().setMessage("initial commit").call(); new Git(db).branchCreate().setName("side").call(); - assertEquals("Switched to branch 'side'", execute("git checkout side")); + assertStringArrayEquals("Switched to branch 'side'", + execute("git checkout side")); } @Test public void testCheckoutNewBranch() throws Exception { new Git(db).commit().setMessage("initial commit").call(); - assertEquals("Switched to a new branch 'side'", + assertStringArrayEquals("Switched to a new branch 'side'", execute("git checkout -b side")); } @Test public void testCheckoutNonExistingBranch() throws Exception { - assertEquals( + assertStringArrayEquals( "error: pathspec 'side' did not match any file(s) known to git.", execute("git checkout side")); } @@ -98,19 +100,20 @@ public class CheckoutTest extends CLIRepositoryTestCase { public void testCheckoutNewBranchThatAlreadyExists() throws Exception { new Git(db).commit().setMessage("initial commit").call(); - assertEquals("fatal: A branch named 'master' already exists.", + assertStringArrayEquals( + "fatal: A branch named 'master' already exists.", execute("git checkout -b master")); } @Test public void testCheckoutNewBranchOnBranchToBeBorn() throws Exception { - assertEquals("fatal: You are on a branch yet to be born", + assertStringArrayEquals("fatal: You are on a branch yet to be born", execute("git checkout -b side")); } @Test public void testCheckoutUnresolvedHead() throws Exception { - assertEquals( + assertStringArrayEquals( "error: pathspec 'HEAD' did not match any file(s) known to git.", execute("git checkout HEAD")); } @@ -119,7 +122,7 @@ public class CheckoutTest extends CLIRepositoryTestCase { public void testCheckoutHead() throws Exception { new Git(db).commit().setMessage("initial commit").call(); - assertEquals("", execute("git checkout HEAD")); + assertStringArrayEquals("", execute("git checkout HEAD")); } @Test @@ -139,10 +142,10 @@ public class CheckoutTest extends CLIRepositoryTestCase { git.add().addFilepattern(".").call(); String[] execute = execute("git checkout branch_1"); - Assert.assertEquals( + assertEquals( "error: Your local changes to the following files would be overwritten by checkout:", execute[0]); - Assert.assertEquals("\ta", execute[1]); + assertEquals("\ta", execute[1]); } /** @@ -193,7 +196,7 @@ public class CheckoutTest extends CLIRepositoryTestCase { Git git = new Git(db); git.commit().setMessage("initial commit").call(); - assertEquals("Switched to a new branch 'new_branch'", + assertStringArrayEquals("Switched to a new branch 'new_branch'", execute("git checkout --orphan new_branch")); assertEquals("refs/heads/new_branch", db.getRef("HEAD").getTarget().getName()); RevCommit commit = git.commit().setMessage("orphan commit").call(); @@ -553,17 +556,13 @@ public class CheckoutTest extends CLIRepositoryTestCase { // assertEquals("a/c", exception.getConflictingPaths().get(1)); } - static private void assertEquals(Object expected, Object actual) { - Assert.assertEquals(expected, actual); - } - - static private void assertEquals(String expected, String[] actual) { + static private void assertStringArrayEquals(String expected, String[] actual) { // if there is more than one line, ignore last one if empty - Assert.assertEquals( + assertEquals( 1, actual.length > 1 && actual[actual.length - 1].equals("") ? actual.length - 1 : actual.length); - Assert.assertEquals(expected, actual[0]); + assertEquals(expected, actual[0]); } @Test diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs index ff39d16ab7..4e28e0b26b 100644 --- a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs @@ -1,10 +1,10 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 @@ -67,11 +67,11 @@ org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullReference=error org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error -org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=error org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 746fd2b0ef..7d00a96ba7 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -2,44 +2,46 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.pgm -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Vendor: %provider_name +Bundle-ActivationPolicy: lazy Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.apache.commons.compress.archivers.tar;version="[1.3,2.0)", org.apache.commons.compress.archivers.zip;version="[1.3,2.0)", - org.eclipse.jgit.api;version="[4.0.3,4.1.0)", - org.eclipse.jgit.api.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.archive;version="[4.0.3,4.1.0)", - org.eclipse.jgit.awtui;version="[4.0.3,4.1.0)", - org.eclipse.jgit.blame;version="[4.0.3,4.1.0)", - org.eclipse.jgit.diff;version="[4.0.3,4.1.0)", - org.eclipse.jgit.dircache;version="[4.0.3,4.1.0)", - org.eclipse.jgit.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.gitrepo;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.pack;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.merge;version="4.0.3", - org.eclipse.jgit.nls;version="[4.0.3,4.1.0)", - org.eclipse.jgit.notes;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revplot;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk.filter;version="[4.0.3,4.1.0)", - org.eclipse.jgit.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.storage.pack;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.resolver;version="[4.0.3,4.1.0)", - org.eclipse.jgit.treewalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.treewalk.filter;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util.io;version="[4.0.3,4.1.0)", + org.eclipse.jgit.api;version="[4.1.2,4.2.0)", + org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.archive;version="[4.1.2,4.2.0)", + org.eclipse.jgit.awtui;version="[4.1.2,4.2.0)", + org.eclipse.jgit.blame;version="[4.1.2,4.2.0)", + org.eclipse.jgit.diff;version="[4.1.2,4.2.0)", + org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", + org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.gitrepo;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.merge;version="4.1.2", + org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", + org.eclipse.jgit.notes;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk.filter;version="[4.1.2,4.2.0)", + org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.storage.pack;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", + org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", org.kohsuke.args4j;version="[2.0.12,2.1.0)", org.kohsuke.args4j.spi;version="[2.0.15,2.1.0)" -Bundle-ActivationPolicy: lazy -Export-Package: org.eclipse.jgit.console;version="4.0.3", - org.eclipse.jgit.pgm;version="4.0.3"; +Export-Package: org.eclipse.jgit.console;version="4.1.2"; + uses:="org.eclipse.jgit.transport, + org.eclipse.jgit.util", + org.eclipse.jgit.pgm;version="4.1.2"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.pgm.opt, @@ -50,12 +52,15 @@ Export-Package: org.eclipse.jgit.console;version="4.0.3", org.eclipse.jgit.treewalk, javax.swing, org.eclipse.jgit.transport", - org.eclipse.jgit.pgm.debug;version="4.0.3";uses:="org.eclipse.jgit.pgm", - org.eclipse.jgit.pgm.internal;version="4.0.3";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test", - org.eclipse.jgit.pgm.opt;version="4.0.3"; + org.eclipse.jgit.pgm.debug;version="4.1.2"; + uses:="org.eclipse.jgit.util.io, + org.eclipse.jgit.pgm", + org.eclipse.jgit.pgm.internal;version="4.1.2";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test", + org.eclipse.jgit.pgm.opt;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.kohsuke.args4j.spi, org.kohsuke.args4j" Main-Class: org.eclipse.jgit.pgm.Main Implementation-Title: JGit Command Line Interface +Require-Bundle: org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF index c68c9f4218..215f8e7ec6 100644 --- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF @@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.jgit.pgm - Sources Bundle-SymbolicName: org.eclipse.jgit.pgm.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.0.3.201509231615-r -Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.0.3.201509231615-r";roots="." +Bundle-Version: 4.1.2.201602141800-r +Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.1.2.201602141800-r";roots="." diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml index 9d8485f4c3..f84e7469fc 100644 --- a/org.eclipse.jgit.pgm/pom.xml +++ b/org.eclipse.jgit.pgm/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.pgm</artifactId> diff --git a/org.eclipse.jgit.pgm/resources/log4j.properties b/org.eclipse.jgit.pgm/resources/log4j.properties index 4a2e48011a..1496c5a2cf 100644 --- a/org.eclipse.jgit.pgm/resources/log4j.properties +++ b/org.eclipse.jgit.pgm/resources/log4j.properties @@ -1,4 +1,4 @@ -log4j.rootLogger=WARNING, stderr +log4j.rootLogger=WARN, stderr log4j.appender.stderr=org.apache.log4j.ConsoleAppender log4j.appender.stderr.Target=System.err diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java index 12aac77e9d..c36c485197 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java @@ -62,10 +62,12 @@ class Add extends TextBuiltin { @Override protected void run() throws Exception { - AddCommand addCmd = new Git(db).add(); - addCmd.setUpdate(update); - for (String p : filepatterns) - addCmd.addFilepattern(p); - addCmd.call(); + try (Git git = new Git(db)) { + AddCommand addCmd = git.add(); + addCmd.setUpdate(update); + for (String p : filepatterns) + addCmd.addFilepattern(p); + addCmd.call(); + } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java index 80bb9ec9df..fe2ba83bc6 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Archive.java @@ -87,8 +87,8 @@ class Archive extends TextBuiltin { else stream = outs; - try { - ArchiveCommand cmd = new Git(db).archive() + try (Git git = new Git(db)) { + ArchiveCommand cmd = git.archive() .setTree(tree) .setFormat(format) .setPrefix(prefix) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java index 72e37158cd..83a1ca7e25 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java @@ -186,29 +186,31 @@ class Branch extends TextBuiltin { // This can happen if HEAD is stillborn if (head != null) { String current = head.getLeaf().getName(); - ListBranchCommand command = new Git(db).branchList(); - if (all) - command.setListMode(ListMode.ALL); - else if (remote) - command.setListMode(ListMode.REMOTE); + try (Git git = new Git(db)) { + ListBranchCommand command = git.branchList(); + if (all) + command.setListMode(ListMode.ALL); + else if (remote) + command.setListMode(ListMode.REMOTE); - if (containsCommitish != null) - command.setContains(containsCommitish); + if (containsCommitish != null) + command.setContains(containsCommitish); - List<Ref> refs = command.call(); - for (Ref ref : refs) { - if (ref.getName().equals(Constants.HEAD)) - addRef("(no branch)", head); //$NON-NLS-1$ - } + List<Ref> refs = command.call(); + for (Ref ref : refs) { + if (ref.getName().equals(Constants.HEAD)) + addRef("(no branch)", head); //$NON-NLS-1$ + } - addRefs(refs, Constants.R_HEADS); - addRefs(refs, Constants.R_REMOTES); + addRefs(refs, Constants.R_HEADS); + addRefs(refs, Constants.R_REMOTES); - try (ObjectReader reader = db.newObjectReader()) { - for (final Entry<String, Ref> e : printRefs.entrySet()) { - final Ref ref = e.getValue(); - printHead(reader, e.getKey(), - current.equals(ref.getName()), ref); + try (ObjectReader reader = db.newObjectReader()) { + for (final Entry<String, Ref> e : printRefs.entrySet()) { + final Ref ref = e.getValue(); + printHead(reader, e.getKey(), + current.equals(ref.getName()), ref); + } } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java index 56d4fcff02..45794629ec 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java @@ -89,47 +89,49 @@ class Checkout extends TextBuiltin { throw die(CLIText.get().onBranchToBeBorn); } - CheckoutCommand command = new Git(db).checkout(); - if (paths.size() > 0) { - command.setStartPoint(name); - for (String path : paths) - command.addPath(path); - } else { - command.setCreateBranch(createBranch); - command.setName(name); - command.setForce(force); - command.setOrphan(orphan); - } - try { - String oldBranch = db.getBranch(); - Ref ref = command.call(); - if (ref == null) - return; - if (Repository.shortenRefName(ref.getName()).equals(oldBranch)) { + try (Git git = new Git(db)) { + CheckoutCommand command = git.checkout(); + if (paths.size() > 0) { + command.setStartPoint(name); + for (String path : paths) + command.addPath(path); + } else { + command.setCreateBranch(createBranch); + command.setName(name); + command.setForce(force); + command.setOrphan(orphan); + } + try { + String oldBranch = db.getBranch(); + Ref ref = command.call(); + if (ref == null) + return; + if (Repository.shortenRefName(ref.getName()).equals(oldBranch)) { + outw.println(MessageFormat.format( + CLIText.get().alreadyOnBranch, + name)); + return; + } + if (createBranch || orphan) + outw.println(MessageFormat.format( + CLIText.get().switchedToNewBranch, name)); + else + outw.println(MessageFormat.format( + CLIText.get().switchedToBranch, + Repository.shortenRefName(ref.getName()))); + } catch (RefNotFoundException e) { outw.println(MessageFormat.format( - CLIText.get().alreadyOnBranch, + CLIText.get().pathspecDidNotMatch, + name)); + } catch (RefAlreadyExistsException e) { + throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, name)); - return; + } catch (CheckoutConflictException e) { + outw.println(CLIText.get().checkoutConflict); + for (String path : e.getConflictingPaths()) + outw.println(MessageFormat.format( + CLIText.get().checkoutConflictPathLine, path)); } - if (createBranch || orphan) - outw.println(MessageFormat.format( - CLIText.get().switchedToNewBranch, name)); - else - outw.println(MessageFormat.format( - CLIText.get().switchedToBranch, - Repository.shortenRefName(ref.getName()))); - } catch (RefNotFoundException e) { - outw.println(MessageFormat.format( - CLIText.get().pathspecDidNotMatch, - name)); - } catch (RefAlreadyExistsException e) { - throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, - name)); - } catch (CheckoutConflictException e) { - outw.println(CLIText.get().checkoutConflict); - for (String path : e.getConflictingPaths()) - outw.println(MessageFormat.format( - CLIText.get().checkoutConflictPathLine, path)); } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java index 14c449a6b3..f18242d684 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java @@ -80,37 +80,39 @@ class Commit extends TextBuiltin { @Override protected void run() throws NoHeadException, NoMessageException, ConcurrentRefUpdateException, JGitInternalException, Exception { - CommitCommand commitCmd = new Git(db).commit(); - if (author != null) - commitCmd.setAuthor(RawParseUtils.parsePersonIdent(author)); - if (message != null) - commitCmd.setMessage(message); - if (only && paths.isEmpty()) - throw die(CLIText.get().pathsRequired); - if (only && all) - throw die(CLIText.get().onlyOneOfIncludeOnlyAllInteractiveCanBeUsed); - if (!paths.isEmpty()) - for (String p : paths) - commitCmd.setOnly(p); - commitCmd.setAmend(amend); - commitCmd.setAll(all); - Ref head = db.getRef(Constants.HEAD); - RevCommit commit; - try { - commit = commitCmd.call(); - } catch (JGitInternalException e) { - throw die(e.getMessage()); - } + try (Git git = new Git(db)) { + CommitCommand commitCmd = git.commit(); + if (author != null) + commitCmd.setAuthor(RawParseUtils.parsePersonIdent(author)); + if (message != null) + commitCmd.setMessage(message); + if (only && paths.isEmpty()) + throw die(CLIText.get().pathsRequired); + if (only && all) + throw die(CLIText.get().onlyOneOfIncludeOnlyAllInteractiveCanBeUsed); + if (!paths.isEmpty()) + for (String p : paths) + commitCmd.setOnly(p); + commitCmd.setAmend(amend); + commitCmd.setAll(all); + Ref head = db.getRef(Constants.HEAD); + RevCommit commit; + try { + commit = commitCmd.call(); + } catch (JGitInternalException e) { + throw die(e.getMessage()); + } - String branchName; - if (!head.isSymbolic()) - branchName = CLIText.get().branchDetachedHEAD; - else { - branchName = head.getTarget().getName(); - if (branchName.startsWith(Constants.R_HEADS)) - branchName = branchName.substring(Constants.R_HEADS.length()); + String branchName; + if (!head.isSymbolic()) + branchName = CLIText.get().branchDetachedHEAD; + else { + branchName = head.getTarget().getName(); + if (branchName.startsWith(Constants.R_HEADS)) + branchName = branchName.substring(Constants.R_HEADS.length()); + } + outw.println("[" + branchName + " " + commit.name() + "] " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + commit.getShortMessage()); } - outw.println("[" + branchName + " " + commit.name() + "] " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - + commit.getShortMessage()); } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java index 901e5604ae..ec000f388b 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java @@ -61,20 +61,21 @@ class Describe extends TextBuiltin { @Override protected void run() throws Exception { - DescribeCommand cmd = new Git(db).describe(); - if (tree != null) - cmd.setTarget(tree); - cmd.setLong(longDesc); - String result = null; - try { - result = cmd.call(); - } catch (RefNotFoundException e) { - throw die(CLIText.get().noNamesFound, e); - } - if (result == null) - throw die(CLIText.get().noNamesFound); + try (Git git = new Git(db)) { + DescribeCommand cmd = git.describe(); + if (tree != null) + cmd.setTarget(tree); + cmd.setLong(longDesc); + String result = null; + try { + result = cmd.call(); + } catch (RefNotFoundException e) { + throw die(CLIText.get().noNamesFound, e); + } + if (result == null) + throw die(CLIText.get().noNamesFound); - outw.println(result); + outw.println(result); + } } - } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java index d89053c4cb..32adf6df0c 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java @@ -74,46 +74,47 @@ class DiffTree extends TextBuiltin { @Override protected void run() throws Exception { - final TreeWalk walk = new TreeWalk(db); - walk.setRecursive(recursive); - for (final AbstractTreeIterator i : trees) - walk.addTree(i); - walk.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, pathFilter)); + try (final TreeWalk walk = new TreeWalk(db)) { + walk.setRecursive(recursive); + for (final AbstractTreeIterator i : trees) + walk.addTree(i); + walk.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, pathFilter)); - final int nTree = walk.getTreeCount(); - while (walk.next()) { - for (int i = 1; i < nTree; i++) - outw.print(':'); - for (int i = 0; i < nTree; i++) { - final FileMode m = walk.getFileMode(i); - final String s = m.toString(); - for (int pad = 6 - s.length(); pad > 0; pad--) - outw.print('0'); - outw.print(s); - outw.print(' '); - } + final int nTree = walk.getTreeCount(); + while (walk.next()) { + for (int i = 1; i < nTree; i++) + outw.print(':'); + for (int i = 0; i < nTree; i++) { + final FileMode m = walk.getFileMode(i); + final String s = m.toString(); + for (int pad = 6 - s.length(); pad > 0; pad--) + outw.print('0'); + outw.print(s); + outw.print(' '); + } - for (int i = 0; i < nTree; i++) { - outw.print(walk.getObjectId(i).name()); - outw.print(' '); - } + for (int i = 0; i < nTree; i++) { + outw.print(walk.getObjectId(i).name()); + outw.print(' '); + } - char chg = 'M'; - if (nTree == 2) { - final int m0 = walk.getRawMode(0); - final int m1 = walk.getRawMode(1); - if (m0 == 0 && m1 != 0) - chg = 'A'; - else if (m0 != 0 && m1 == 0) - chg = 'D'; - else if (m0 != m1 && walk.idEqual(0, 1)) - chg = 'T'; - } - outw.print(chg); + char chg = 'M'; + if (nTree == 2) { + final int m0 = walk.getRawMode(0); + final int m1 = walk.getRawMode(1); + if (m0 == 0 && m1 != 0) + chg = 'A'; + else if (m0 != 0 && m1 == 0) + chg = 'D'; + else if (m0 != m1 && walk.idEqual(0, 1)) + chg = 'T'; + } + outw.print(chg); - outw.print('\t'); - outw.print(walk.getPathString()); - outw.println(); + outw.print('\t'); + outw.print(walk.getPathString()); + outw.println(); + } } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java index 186fdd8a22..ed06733a44 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java @@ -104,31 +104,32 @@ class Fetch extends AbstractFetchCommand { @Override protected void run() throws Exception { - Git git = new Git(db); - FetchCommand fetch = git.fetch(); - if (fsck != null) - fetch.setCheckFetchedObjects(fsck.booleanValue()); - if (prune != null) - fetch.setRemoveDeletedRefs(prune.booleanValue()); - if (toget != null) - fetch.setRefSpecs(toget); - if (tags != null) { - fetch.setTagOpt(tags.booleanValue() ? TagOpt.FETCH_TAGS - : TagOpt.NO_TAGS); + try (Git git = new Git(db)) { + FetchCommand fetch = git.fetch(); + if (fsck != null) + fetch.setCheckFetchedObjects(fsck.booleanValue()); + if (prune != null) + fetch.setRemoveDeletedRefs(prune.booleanValue()); + if (toget != null) + fetch.setRefSpecs(toget); + if (tags != null) { + fetch.setTagOpt(tags.booleanValue() ? TagOpt.FETCH_TAGS + : TagOpt.NO_TAGS); + } + if (0 <= timeout) + fetch.setTimeout(timeout); + fetch.setDryRun(dryRun); + fetch.setRemote(remote); + if (thin != null) + fetch.setThin(thin.booleanValue()); + if (quiet == null || !quiet.booleanValue()) + fetch.setProgressMonitor(new TextProgressMonitor(errw)); + + FetchResult result = fetch.call(); + if (result.getTrackingRefUpdates().isEmpty()) + return; + + showFetchResult(result); } - if (0 <= timeout) - fetch.setTimeout(timeout); - fetch.setDryRun(dryRun); - fetch.setRemote(remote); - if (thin != null) - fetch.setThin(thin.booleanValue()); - if (quiet == null || !quiet.booleanValue()) - fetch.setProgressMonitor(new TextProgressMonitor(errw)); - - FetchResult result = fetch.call(); - if (result.getTrackingRefUpdates().isEmpty()) - return; - - showFetchResult(result); } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java index 4b16ed8800..872ea67774 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java @@ -72,27 +72,28 @@ class LsTree extends TextBuiltin { @Override protected void run() throws Exception { - final TreeWalk walk = new TreeWalk(db); - walk.reset(); // drop the first empty tree, which we do not need here - if (paths.size() > 0) - walk.setFilter(PathFilterGroup.createFromStrings(paths)); - walk.setRecursive(recursive); - walk.addTree(tree); + try (final TreeWalk walk = new TreeWalk(db)) { + walk.reset(); // drop the first empty tree, which we do not need here + if (paths.size() > 0) + walk.setFilter(PathFilterGroup.createFromStrings(paths)); + walk.setRecursive(recursive); + walk.addTree(tree); - while (walk.next()) { - final FileMode mode = walk.getFileMode(0); - if (mode == FileMode.TREE) - outw.print('0'); - outw.print(mode); - outw.print(' '); - outw.print(Constants.typeString(mode.getObjectType())); + while (walk.next()) { + final FileMode mode = walk.getFileMode(0); + if (mode == FileMode.TREE) + outw.print('0'); + outw.print(mode); + outw.print(' '); + outw.print(Constants.typeString(mode.getObjectType())); - outw.print(' '); - outw.print(walk.getObjectId(0).name()); + outw.print(' '); + outw.print(walk.getObjectId(0).name()); - outw.print('\t'); - outw.print(QuotedString.GIT_PATH.quote(walk.getPathString())); - outw.println(); + outw.print('\t'); + outw.print(QuotedString.GIT_PATH.quote(walk.getPathString())); + outw.println(); + } } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index 7151de794d..ceb0d6b2fe 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -181,7 +181,7 @@ public class Main { if (argv.length == 0 || help) { final String ex = clp.printExample(ExampleMode.ALL, CLIText.get().resourceBundle()); - writer.println("jgit" + ex + " command [ARG ...]"); //$NON-NLS-1$ + writer.println("jgit" + ex + " command [ARG ...]"); //$NON-NLS-1$ //$NON-NLS-2$ if (help) { writer.println(); clp.printUsage(writer, CLIText.get().resourceBundle()); @@ -291,39 +291,45 @@ public class Main { /** * Configure the JRE's standard HTTP based on <code>http_proxy</code>. * <p> - * The popular libcurl library honors the <code>http_proxy</code> - * environment variable as a means of specifying an HTTP proxy for requests - * made behind a firewall. This is not natively recognized by the JRE, so - * this method can be used by command line utilities to configure the JRE - * before the first request is sent. + * The popular libcurl library honors the <code>http_proxy</code>, + * <code>https_proxy</code> environment variables as a means of specifying + * an HTTP/S proxy for requests made behind a firewall. This is not natively + * recognized by the JRE, so this method can be used by command line + * utilities to configure the JRE before the first request is sent. * * @throws MalformedURLException - * the value in <code>http_proxy</code> is unsupportable. + * the value in <code>http_proxy</code> or + * <code>https_proxy</code> is unsupportable. */ private static void configureHttpProxy() throws MalformedURLException { - final String s = System.getenv("http_proxy"); //$NON-NLS-1$ - if (s == null || s.equals("")) //$NON-NLS-1$ - return; + for (String protocol : new String[] { "http", "https" }) { //$NON-NLS-1$ //$NON-NLS-2$ + final String s = System.getenv(protocol + "_proxy"); //$NON-NLS-1$ + if (s == null || s.equals("")) //$NON-NLS-1$ + return; - final URL u = new URL((s.indexOf("://") == -1) ? "http://" + s : s); //$NON-NLS-1$ //$NON-NLS-2$ - if (!"http".equals(u.getProtocol())) //$NON-NLS-1$ - throw new MalformedURLException(MessageFormat.format(CLIText.get().invalidHttpProxyOnlyHttpSupported, s)); + final URL u = new URL( + (s.indexOf("://") == -1) ? protocol + "://" + s : s); //$NON-NLS-1$ //$NON-NLS-2$ + if (!u.getProtocol().startsWith("http")) //$NON-NLS-1$ + throw new MalformedURLException(MessageFormat.format( + CLIText.get().invalidHttpProxyOnlyHttpSupported, s)); - final String proxyHost = u.getHost(); - final int proxyPort = u.getPort(); + final String proxyHost = u.getHost(); + final int proxyPort = u.getPort(); - System.setProperty("http.proxyHost", proxyHost); //$NON-NLS-1$ - if (proxyPort > 0) - System.setProperty("http.proxyPort", String.valueOf(proxyPort)); //$NON-NLS-1$ + System.setProperty(protocol + ".proxyHost", proxyHost); //$NON-NLS-1$ + if (proxyPort > 0) + System.setProperty(protocol + ".proxyPort", //$NON-NLS-1$ + String.valueOf(proxyPort)); - final String userpass = u.getUserInfo(); - if (userpass != null && userpass.contains(":")) { //$NON-NLS-1$ - final int c = userpass.indexOf(':'); - final String user = userpass.substring(0, c); - final String pass = userpass.substring(c + 1); - CachedAuthenticator - .add(new CachedAuthenticator.CachedAuthentication( - proxyHost, proxyPort, user, pass)); + final String userpass = u.getUserInfo(); + if (userpass != null && userpass.contains(":")) { //$NON-NLS-1$ + final int c = userpass.indexOf(':'); + final String user = userpass.substring(0, c); + final String pass = userpass.substring(c + 1); + CachedAuthenticator.add( + new CachedAuthenticator.CachedAuthentication(proxyHost, + proxyPort, user, pass)); + } } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java index 93c4388dbc..e0ff0583cb 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java @@ -121,22 +121,23 @@ class Merge extends TextBuiltin { CLIText.get().refDoesNotExistOrNoCommit, ref)); Ref oldHead = db.getRef(Constants.HEAD); - Git git = new Git(db); - MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy) - .setSquash(squash).setFastForward(ff).setCommit(!noCommit); - if (srcRef != null) - mergeCmd.include(srcRef); - else - mergeCmd.include(src); + MergeResult result; + try (Git git = new Git(db)) { + MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy) + .setSquash(squash).setFastForward(ff).setCommit(!noCommit); + if (srcRef != null) + mergeCmd.include(srcRef); + else + mergeCmd.include(src); - if (message != null) - mergeCmd.setMessage(message); + if (message != null) + mergeCmd.setMessage(message); - MergeResult result; - try { - result = mergeCmd.call(); - } catch (CheckoutConflictException e) { - result = new MergeResult(e.getConflictingPaths()); // CHECKOUT_CONFLICT + try { + result = mergeCmd.call(); + } catch (CheckoutConflictException e) { + result = new MergeResult(e.getConflictingPaths()); // CHECKOUT_CONFLICT + } } switch (result.getMergeStatus()) { @@ -206,12 +207,13 @@ class Merge extends TextBuiltin { private boolean isMergedInto(Ref oldHead, AnyObjectId src) throws IOException { - RevWalk revWalk = new RevWalk(db); - ObjectId oldHeadObjectId = oldHead.getPeeledObjectId(); - if (oldHeadObjectId == null) - oldHeadObjectId = oldHead.getObjectId(); - RevCommit oldHeadCommit = revWalk.lookupCommit(oldHeadObjectId); - RevCommit srcCommit = revWalk.lookupCommit(src); - return revWalk.isMergedInto(oldHeadCommit, srcCommit); + try (RevWalk revWalk = new RevWalk(db)) { + ObjectId oldHeadObjectId = oldHead.getPeeledObjectId(); + if (oldHeadObjectId == null) + oldHeadObjectId = oldHead.getObjectId(); + RevCommit oldHeadCommit = revWalk.lookupCommit(oldHeadObjectId); + RevCommit srcCommit = revWalk.lookupCommit(src); + return revWalk.isMergedInto(oldHeadCommit, srcCommit); + } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java index 4268f214fd..1879ef51ff 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java @@ -109,24 +109,25 @@ class Push extends TextBuiltin { @Override protected void run() throws Exception { - Git git = new Git(db); - PushCommand push = git.push(); - push.setDryRun(dryRun); - push.setForce(force); - push.setProgressMonitor(new TextProgressMonitor(errw)); - push.setReceivePack(receivePack); - push.setRefSpecs(refSpecs); - if (all) - push.setPushAll(); - if (tags) - push.setPushTags(); - push.setRemote(remote); - push.setThin(thin); - push.setTimeout(timeout); - Iterable<PushResult> results = push.call(); - for (PushResult result : results) { - try (ObjectReader reader = db.newObjectReader()) { - printPushResult(reader, result.getURI(), result); + try (Git git = new Git(db)) { + PushCommand push = git.push(); + push.setDryRun(dryRun); + push.setForce(force); + push.setProgressMonitor(new TextProgressMonitor(errw)); + push.setReceivePack(receivePack); + push.setRefSpecs(refSpecs); + if (all) + push.setPushAll(); + if (tags) + push.setPushTags(); + push.setRemote(remote); + push.setThin(thin); + push.setTimeout(timeout); + Iterable<PushResult> results = push.call(); + for (PushResult result : results) { + try (ObjectReader reader = db.newObjectReader()) { + printPushResult(reader, result.getURI(), result); + } } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java index aa90f8d50c..86a021dee1 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reflog.java @@ -59,13 +59,15 @@ class Reflog extends TextBuiltin { @Override protected void run() throws Exception { - ReflogCommand cmd = new Git(db).reflog(); - if (ref != null) - cmd.setRef(ref); - Collection<ReflogEntry> entries = cmd.call(); - int i = 0; - for (ReflogEntry entry : entries) { - outw.println(toString(entry, i++)); + try (Git git = new Git(db)) { + ReflogCommand cmd = git.reflog(); + if (ref != null) + cmd.setRef(ref); + Collection<ReflogEntry> entries = cmd.call(); + int i = 0; + for (ReflogEntry entry : entries) { + outw.println(toString(entry, i++)); + } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java index f4cbcafed1..6d1b1c5481 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java @@ -66,19 +66,21 @@ class Reset extends TextBuiltin { @Override protected void run() throws Exception { - ResetCommand command = new Git(db).reset(); - command.setRef(commit); - ResetType mode = null; - if (soft) - mode = selectMode(mode, ResetType.SOFT); - if (mixed) - mode = selectMode(mode, ResetType.MIXED); - if (hard) - mode = selectMode(mode, ResetType.HARD); - if (mode == null) - throw die("no reset mode set"); - command.setMode(mode); - command.call(); + try (Git git = new Git(db)) { + ResetCommand command = git.reset(); + command.setRef(commit); + ResetType mode = null; + if (soft) + mode = selectMode(mode, ResetType.SOFT); + if (mixed) + mode = selectMode(mode, ResetType.MIXED); + if (hard) + mode = selectMode(mode, ResetType.HARD); + if (mode == null) + throw die("no reset mode set"); + command.setMode(mode); + command.call(); + } } private static ResetType selectMode(ResetType mode, ResetType want) { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java index 816b3104c2..f4f864b397 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java @@ -63,10 +63,11 @@ class Rm extends TextBuiltin { @Override protected void run() throws Exception { - RmCommand command = new Git(db).rm(); - for (String p : paths) - command.addFilepattern(p); - command.call(); + try (Git git = new Git(db)) { + RmCommand command = git.rm(); + for (String p : paths) + command.addFilepattern(p); + command.call(); + } } - } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java index b668139b5e..c5986b01aa 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java @@ -251,16 +251,17 @@ class Show extends TextBuiltin { private void show(RevTree obj) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { - final TreeWalk walk = new TreeWalk(db); - walk.reset(); - walk.addTree(obj); - - while (walk.next()) { - outw.print(walk.getPathString()); - final FileMode mode = walk.getFileMode(0); - if (mode == FileMode.TREE) - outw.print("/"); //$NON-NLS-1$ - outw.println(); + try (final TreeWalk walk = new TreeWalk(db)) { + walk.reset(); + walk.addTree(obj); + + while (walk.next()) { + outw.print(walk.getPathString()); + final FileMode mode = walk.getFileMode(0); + if (mode == FileMode.TREE) + outw.print("/"); //$NON-NLS-1$ + outw.println(); + } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java index 12d4208152..be82d070f7 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java @@ -88,12 +88,14 @@ class Status extends TextBuiltin { @Override protected void run() throws Exception { - StatusCommand statusCommand = new Git(db).status(); - if (filterPaths != null && filterPaths.size() > 0) - for (String path : filterPaths) - statusCommand.addPath(path); - org.eclipse.jgit.api.Status status = statusCommand.call(); - printStatus(status); + try (Git git = new Git(db)) { + StatusCommand statusCommand = git.status(); + if (filterPaths != null && filterPaths.size() > 0) + for (String path : filterPaths) + statusCommand.addPath(path); + org.eclipse.jgit.api.Status status = statusCommand.call(); + printStatus(status); + } } private void printStatus(org.eclipse.jgit.api.Status status) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java index a90d4c4dad..45fceb570f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java @@ -79,26 +79,28 @@ class Tag extends TextBuiltin { @Override protected void run() throws Exception { - Git git = new Git(db); - if (tagName != null) { - TagCommand command = git.tag().setForceUpdate(force) - .setMessage(message).setName(tagName); + try (Git git = new Git(db)) { + if (tagName != null) { + TagCommand command = git.tag().setForceUpdate(force) + .setMessage(message).setName(tagName); - if (object != null) { - RevWalk walk = new RevWalk(db); - command.setObjectId(walk.parseAny(object)); - } - try { - command.call(); - } catch (RefAlreadyExistsException e) { - throw die(MessageFormat.format(CLIText.get().tagAlreadyExists, - tagName)); - } - } else { - ListTagCommand command = git.tagList(); - List<Ref> list = command.call(); - for (Ref ref : list) { - outw.println(Repository.shortenRefName(ref.getName())); + if (object != null) { + try (RevWalk walk = new RevWalk(db)) { + command.setObjectId(walk.parseAny(object)); + } + } + try { + command.call(); + } catch (RefAlreadyExistsException e) { + throw die(MessageFormat.format(CLIText.get().tagAlreadyExists, + tagName)); + } + } else { + ListTagCommand command = git.tagList(); + List<Ref> list = command.call(); + for (Ref ref : list) { + outw.println(Repository.shortenRefName(ref.getName())); + } } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java index 24d717dfd8..df7ebb78b8 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java @@ -173,9 +173,9 @@ class DiffAlgorithms extends TextBuiltin { int maxN = 0; AbbreviatedObjectId startId; - try (ObjectReader or = db.newObjectReader()) { + try (ObjectReader or = db.newObjectReader(); + RevWalk rw = new RevWalk(or)) { final MutableObjectId id = new MutableObjectId(); - RevWalk rw = new RevWalk(or); TreeWalk tw = new TreeWalk(or); tw.setFilter(TreeFilter.ANY_DIFF); tw.setRecursive(true); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java index 7b5cdbf8f7..d3eb245cd9 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java @@ -75,7 +75,10 @@ class ShowPackDelta extends TextBuiltin { @Override protected void run() throws Exception { ObjectReader reader = db.newObjectReader(); - RevObject obj = new RevWalk(reader).parseAny(objectId); + RevObject obj; + try (RevWalk rw = new RevWalk(reader)) { + obj = rw.parseAny(objectId); + } byte[] delta = getDelta(reader, obj); // We're crossing our fingers that this will be a delta. Double diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java index dcfa8cf00a..dcbc37bed6 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java @@ -300,10 +300,10 @@ class TextHashFunctions extends TextBuiltin { long fileCnt = 0; long lineCnt = 0; - try (ObjectReader or = db.newObjectReader()) { - final MutableObjectId id = new MutableObjectId(); + try (ObjectReader or = db.newObjectReader(); RevWalk rw = new RevWalk(or); - TreeWalk tw = new TreeWalk(or); + TreeWalk tw = new TreeWalk(or)) { + final MutableObjectId id = new MutableObjectId(); tw.reset(rw.parseTree(db.resolve(Constants.HEAD))); tw.setRecursive(true); diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 0c3edefecd..f059fdb321 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -2,51 +2,51 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.test -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", - org.eclipse.jgit.api;version="[4.0.3,4.1.0)", - org.eclipse.jgit.api.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.attributes;version="[4.0.3,4.1.0)", - org.eclipse.jgit.awtui;version="[4.0.3,4.1.0)", - org.eclipse.jgit.blame;version="[4.0.3,4.1.0)", - org.eclipse.jgit.diff;version="[4.0.3,4.1.0)", - org.eclipse.jgit.dircache;version="[4.0.3,4.1.0)", - org.eclipse.jgit.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.events;version="[4.0.3,4.1.0)", - org.eclipse.jgit.fnmatch;version="[4.0.3,4.1.0)", - org.eclipse.jgit.gitrepo;version="[4.0.3,4.1.0)", - org.eclipse.jgit.hooks;version="[4.0.3,4.1.0)", - org.eclipse.jgit.ignore;version="[4.0.3,4.1.0)", - org.eclipse.jgit.ignore.internal;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.dfs;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.internal.storage.pack;version="[4.0.3,4.1.0)", - org.eclipse.jgit.junit;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.merge;version="[4.0.3,4.1.0)", - org.eclipse.jgit.nls;version="[4.0.3,4.1.0)", - org.eclipse.jgit.notes;version="[4.0.3,4.1.0)", - org.eclipse.jgit.patch;version="[4.0.3,4.1.0)", - org.eclipse.jgit.pgm;version="[4.0.3,4.1.0)", - org.eclipse.jgit.pgm.internal;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revplot;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk.filter;version="[4.0.3,4.1.0)", - org.eclipse.jgit.storage.file;version="[4.0.3,4.1.0)", - org.eclipse.jgit.storage.pack;version="[4.0.3,4.1.0)", - org.eclipse.jgit.submodule;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.http;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport.resolver;version="[4.0.3,4.1.0)", - org.eclipse.jgit.treewalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.treewalk.filter;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util.io;version="[4.0.3,4.1.0)", + org.eclipse.jgit.api;version="[4.1.2,4.2.0)", + org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.attributes;version="[4.1.2,4.2.0)", + org.eclipse.jgit.awtui;version="[4.1.2,4.2.0)", + org.eclipse.jgit.blame;version="[4.1.2,4.2.0)", + org.eclipse.jgit.diff;version="[4.1.2,4.2.0)", + org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", + org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.events;version="[4.1.2,4.2.0)", + org.eclipse.jgit.fnmatch;version="[4.1.2,4.2.0)", + org.eclipse.jgit.gitrepo;version="[4.1.2,4.2.0)", + org.eclipse.jgit.hooks;version="[4.1.2,4.2.0)", + org.eclipse.jgit.ignore;version="[4.1.2,4.2.0)", + org.eclipse.jgit.ignore.internal;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.dfs;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)", + org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.merge;version="[4.1.2,4.2.0)", + org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", + org.eclipse.jgit.notes;version="[4.1.2,4.2.0)", + org.eclipse.jgit.patch;version="[4.1.2,4.2.0)", + org.eclipse.jgit.pgm;version="[4.1.2,4.2.0)", + org.eclipse.jgit.pgm.internal;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk.filter;version="[4.1.2,4.2.0)", + org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", + org.eclipse.jgit.storage.pack;version="[4.1.2,4.2.0)", + org.eclipse.jgit.submodule;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", + org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", org.hamcrest;version="[1.1.0,2.0.0)", org.junit;version="[4.4.0,5.0.0)", org.junit.experimental.theories;version="[4.4.0,5.0.0)", diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml index ab77aec7ea..be72211519 100644 --- a/org.eclipse.jgit.test/pom.xml +++ b/org.eclipse.jgit.test/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.test</artifactId> diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java index 3b2fa6c37d..ce11e1b1bc 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java @@ -202,6 +202,66 @@ public class CloneCommandTest extends RepositoryTestCase { fetchRefSpec(git2.getRepository())); } + @Test + public void testCloneRepositoryCustomRemote() throws Exception { + File directory = createTempDirectory("testCloneRemoteUpstream"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setRemote("upstream"); + command.setURI(fileUri()); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + assertEquals("+refs/heads/*:refs/remotes/upstream/*", + git2.getRepository() + .getConfig() + .getStringList("remote", "upstream", + "fetch")[0]); + assertEquals("upstream", + git2.getRepository() + .getConfig() + .getString("branch", "test", "remote")); + assertEquals(db.resolve("test"), + git2.getRepository().resolve("upstream/test")); + } + + @Test + public void testBareCloneRepositoryCustomRemote() throws Exception { + File directory = createTempDirectory("testCloneRemoteUpstream_bare"); + CloneCommand command = Git.cloneRepository(); + command.setBare(true); + command.setDirectory(directory); + command.setRemote("upstream"); + command.setURI(fileUri()); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + assertEquals("+refs/heads/*:refs/heads/*", + git2.getRepository() + .getConfig() + .getStringList("remote", "upstream", + "fetch")[0]); + assertEquals("upstream", + git2.getRepository() + .getConfig() + .getString("branch", "test", "remote")); + assertNull(git2.getRepository().resolve("upstream/test")); + } + + @Test + public void testBareCloneRepositoryNullRemote() throws Exception { + File directory = createTempDirectory("testCloneRemoteNull_bare"); + CloneCommand command = Git.cloneRepository(); + command.setBare(true); + command.setDirectory(directory); + command.setRemote(null); + command.setURI(fileUri()); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + assertEquals("+refs/heads/*:refs/heads/*", git2.getRepository() + .getConfig().getStringList("remote", "origin", "fetch")[0]); + assertEquals("origin", git2.getRepository().getConfig() + .getString("branch", "test", "remote")); + } + public static RefSpec fetchRefSpec(Repository r) throws URISyntaxException { RemoteConfig remoteConfig = new RemoteConfig(r.getConfig(), Constants.DEFAULT_REMOTE_NAME); @@ -391,6 +451,17 @@ public class CloneCommandTest extends RepositoryTestCase { git.add().addFilepattern(path) .addFilepattern(Constants.DOT_GIT_MODULES).call(); git.commit().setMessage("adding submodule").call(); + try (SubmoduleWalk walk = SubmoduleWalk.forIndex(git.getRepository())) { + assertTrue(walk.next()); + Repository subRepo = walk.getRepository(); + addRepoToClose(subRepo); + assertNotNull(subRepo); + assertEquals( + new File(git.getRepository().getWorkTree(), walk.getPath()), + subRepo.getWorkTree()); + assertEquals(new File(new File(git.getRepository().getDirectory(), + "modules"), walk.getPath()), subRepo.getDirectory()); + } File directory = createTempDirectory("testCloneRepositoryWithSubmodules"); CloneCommand clone = Git.cloneRepository(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java index 95b14192c9..ce235a722f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java @@ -609,7 +609,7 @@ public class StashApplyCommandTest extends RepositoryTestCase { } catch (JGitInternalException e) { assertEquals(MessageFormat.format( JGitText.get().stashCommitIncorrectNumberOfParents, - head.name(), 0), + head.name(), "0"), e.getMessage()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java index 02e485d41e..58348d5117 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java @@ -241,8 +241,7 @@ public class DiffFormatterTest extends RepositoryTestCase { ObjectId bId = blob("b\n"); String diffHeader = makeDiffHeaderModeChange(PATH_A, PATH_A, aId, bId, - GITLINK, REGULAR_FILE) - + "-Subproject commit " + aId.name() + "\n"; + GITLINK, REGULAR_FILE); DiffEntry ad = DiffEntry.delete(PATH_A, aId); ad.oldMode = FileMode.GITLINK; @@ -257,7 +256,69 @@ public class DiffFormatterTest extends RepositoryTestCase { assertEquals(1, fh.getHunks().size()); HunkHeader hh = fh.getHunks().get(0); - assertEquals(0, hh.toEditList().size()); + assertEquals(1, hh.toEditList().size()); + } + + @Test + public void testCreateFileHeader_AddGitLink() throws Exception { + ObjectId adId = blob("a\nd\n"); + DiffEntry ent = DiffEntry.add("FOO", adId); + ent.newMode = FileMode.GITLINK; + FileHeader fh = df.toFileHeader(ent); + + String diffHeader = "diff --git a/FOO b/FOO\n" // + + "new file mode " + GITLINK + "\n" + + "index " + + ObjectId.zeroId().abbreviate(8).name() + + ".." + + adId.abbreviate(8).name() + "\n" // + + "--- /dev/null\n"// + + "+++ b/FOO\n"; + assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer())); + + assertEquals(1, fh.getHunks().size()); + HunkHeader hh = fh.getHunks().get(0); + + EditList el = hh.toEditList(); + assertEquals(1, el.size()); + + Edit e = el.get(0); + assertEquals(0, e.getBeginA()); + assertEquals(0, e.getEndA()); + assertEquals(0, e.getBeginB()); + assertEquals(1, e.getEndB()); + assertEquals(Edit.Type.INSERT, e.getType()); + } + + @Test + public void testCreateFileHeader_DeleteGitLink() throws Exception { + ObjectId adId = blob("a\nd\n"); + DiffEntry ent = DiffEntry.delete("FOO", adId); + ent.oldMode = FileMode.GITLINK; + FileHeader fh = df.toFileHeader(ent); + + String diffHeader = "diff --git a/FOO b/FOO\n" // + + "deleted file mode " + GITLINK + "\n" + + "index " + + adId.abbreviate(8).name() + + ".." + + ObjectId.zeroId().abbreviate(8).name() + "\n" // + + "--- a/FOO\n"// + + "+++ /dev/null\n"; + assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer())); + + assertEquals(1, fh.getHunks().size()); + HunkHeader hh = fh.getHunks().get(0); + + EditList el = hh.toEditList(); + assertEquals(1, el.size()); + + Edit e = el.get(0); + assertEquals(0, e.getBeginA()); + assertEquals(1, e.getEndA()); + assertEquals(0, e.getBeginB()); + assertEquals(0, e.getEndB()); + assertEquals(Edit.Type.DELETE, e.getType()); } @Test diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java index 3d86cfd5bb..66e7256432 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java @@ -241,9 +241,9 @@ public class RepoCommandTest extends RepositoryTestCase { @Test public void testBareRepo() throws Exception { - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository(); - try { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { StringBuilder xmlContent = new StringBuilder(); xmlContent .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") @@ -280,9 +280,6 @@ public class RepoCommandTest extends RepositoryTestCase { String remote = defaultDb.resolve(Constants.HEAD).name(); assertEquals("The gitlink should be the same as remote head", remote, gitlink); - } finally { - tempDb.close(); - remoteDb.close(); } } @@ -366,9 +363,9 @@ public class RepoCommandTest extends RepositoryTestCase { @Test public void testRevisionBare() throws Exception { - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository(); - try { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { StringBuilder xmlContent = new StringBuilder(); xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") .append("<manifest>") @@ -393,17 +390,14 @@ public class RepoCommandTest extends RepositoryTestCase { localDb.close(); assertEquals("The gitlink is same as remote head", oldCommitId.name(), gitlink); - } finally { - tempDb.close(); - remoteDb.close(); } } @Test public void testCopyFileBare() throws Exception { - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository(); - try { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { StringBuilder xmlContent = new StringBuilder(); xmlContent .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") @@ -435,17 +429,14 @@ public class RepoCommandTest extends RepositoryTestCase { reader.close(); assertEquals("The Hello file should have expected content", "branch world", content); - } finally { - tempDb.close(); - remoteDb.close(); } } @Test public void testReplaceManifestBare() throws Exception { - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository(); - try { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { StringBuilder xmlContent = new StringBuilder(); xmlContent .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") @@ -512,17 +503,14 @@ public class RepoCommandTest extends RepositoryTestCase { reader.close(); assertTrue("The bar submodule should exist", bar); assertFalse("The foo submodule shouldn't exist", foo); - } finally { - tempDb.close(); - remoteDb.close(); } } @Test public void testRemoveOverlappingBare() throws Exception { - Repository remoteDb = createBareRepository(); - Repository tempDb = createWorkRepository(); - try { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { StringBuilder xmlContent = new StringBuilder(); xmlContent .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") @@ -571,9 +559,6 @@ public class RepoCommandTest extends RepositoryTestCase { assertTrue("The foo submodule should exist", foo); assertFalse("The foo/bar submodule shouldn't exist", foobar); assertTrue("The a submodule should exist", a); - } finally { - tempDb.close(); - remoteDb.close(); } } @@ -671,6 +656,42 @@ public class RepoCommandTest extends RepositoryTestCase { assertTrue("We should have foo", file.exists()); } + @Test + public void testTargetBranch() throws Exception { + try ( + Repository remoteDb1 = createBareRepository(); + Repository remoteDb2 = createBareRepository(); + Repository tempDb = createWorkRepository()) { + StringBuilder xmlContent = new StringBuilder(); + xmlContent + .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") + .append("<manifest>") + .append("<remote name=\"remote1\" fetch=\".\" />") + .append("<default revision=\"master\" remote=\"remote1\" />") + .append("<project path=\"foo\" name=\"").append(defaultUri) + .append("\" />").append("</manifest>"); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb1); + command + .setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .setTargetBranch("test") + .call(); + ObjectId branchId = remoteDb1.resolve( + Constants.R_HEADS + "test^{tree}"); + command = new RepoCommand(remoteDb2); + command + .setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .call(); + ObjectId defaultId = remoteDb2.resolve(Constants.HEAD + "^{tree}"); + assertEquals( + "The tree id of branch db and default db should be the same", + branchId, defaultId); + } + } + private void resolveRelativeUris() { // Find the longest common prefix ends with "/" as rootUri. defaultUri = defaultDb.getDirectory().toURI().toString(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java index a4b799a72e..2c04787e3d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java @@ -55,6 +55,7 @@ public class FastIgnoreRuleTest { @Test public void testSimpleCharClass() { assertMatched("[a]", "a"); + assertMatched("][a]", "]a"); assertMatched("[a]", "a/"); assertMatched("[a]", "a/b"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java index 571f3186d7..9722ac6750 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java @@ -44,12 +44,15 @@ package org.eclipse.jgit.ignore; import static org.eclipse.jgit.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; import org.eclipse.jgit.errors.CorruptObjectException; @@ -324,6 +327,15 @@ public class IgnoreNodeTest extends RepositoryTestCase { } @Test + public void testEmptyIgnoreRules() throws IOException { + IgnoreNode node = new IgnoreNode(); + node.parse(writeToString("", "#", "!", "[[=a=]]")); + assertEquals(new ArrayList<>(), node.getRules()); + node.parse(writeToString(" ", " / ")); + assertEquals(2, node.getRules().size()); + } + + @Test public void testSlashOnlyMatchesDirectory() throws IOException { writeIgnoreFile(".gitignore", "out/"); writeTrashFile("out", ""); @@ -343,6 +355,56 @@ public class IgnoreNodeTest extends RepositoryTestCase { } @Test + public void testSlashMatchesDirectory() throws IOException { + writeIgnoreFile(".gitignore", "out2/"); + + writeTrashFile("out1/out1", ""); + writeTrashFile("out1/out2", ""); + writeTrashFile("out2/out1", ""); + writeTrashFile("out2/out2", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "out1"); + assertEntry(F, tracked, "out1/out1"); + assertEntry(F, tracked, "out1/out2"); + assertEntry(D, ignored, "out2"); + assertEntry(F, ignored, "out2/out1"); + assertEntry(F, ignored, "out2/out2"); + endWalk(); + } + + @Test + public void testWildcardWithSlashMatchesDirectory() throws IOException { + writeIgnoreFile(".gitignore", "out2*/"); + + writeTrashFile("out1/out1.txt", ""); + writeTrashFile("out1/out2", ""); + writeTrashFile("out1/out2.txt", ""); + writeTrashFile("out1/out2x/a", ""); + writeTrashFile("out2/out1.txt", ""); + writeTrashFile("out2/out2.txt", ""); + writeTrashFile("out2x/out1.txt", ""); + writeTrashFile("out2x/out2.txt", ""); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "out1"); + assertEntry(F, tracked, "out1/out1.txt"); + assertEntry(F, tracked, "out1/out2"); + assertEntry(F, tracked, "out1/out2.txt"); + assertEntry(D, ignored, "out1/out2x"); + assertEntry(F, ignored, "out1/out2x/a"); + assertEntry(D, ignored, "out2"); + assertEntry(F, ignored, "out2/out1.txt"); + assertEntry(F, ignored, "out2/out2.txt"); + assertEntry(D, ignored, "out2x"); + assertEntry(F, ignored, "out2x/out1.txt"); + assertEntry(F, ignored, "out2x/out2.txt"); + endWalk(); + } + + @Test public void testWithSlashDoesNotMatchInSubDirectory() throws IOException { writeIgnoreFile(".gitignore", "a/b"); writeTrashFile("a/a", ""); @@ -375,6 +437,67 @@ public class IgnoreNodeTest extends RepositoryTestCase { } @Test + public void testLeadingSpaces() throws IOException { + writeTrashFile(" a/ a", ""); + writeTrashFile(" a/ a", ""); + writeTrashFile(" a/a", ""); + writeTrashFile(" a/ a", ""); + writeTrashFile(" a/ a", ""); + writeTrashFile(" a/a", ""); + writeIgnoreFile(".gitignore", " a", " a"); + writeTrashFile("a/ a", ""); + writeTrashFile("a/ a", ""); + writeTrashFile("a/a", ""); + + beginWalk(); + assertEntry(D, ignored, " a"); + assertEntry(F, ignored, " a/ a"); + assertEntry(F, ignored, " a/ a"); + assertEntry(F, ignored, " a/a"); + assertEntry(D, ignored, " a"); + assertEntry(F, ignored, " a/ a"); + assertEntry(F, ignored, " a/ a"); + assertEntry(F, ignored, " a/a"); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, tracked, "a"); + assertEntry(F, ignored, "a/ a"); + assertEntry(F, ignored, "a/ a"); + assertEntry(F, tracked, "a/a"); + endWalk(); + } + + @Test + public void testTrailingSpaces() throws IOException { + writeTrashFile("a /a", ""); + writeTrashFile("a /a ", ""); + writeTrashFile("a /a ", ""); + writeTrashFile("a /a", ""); + writeTrashFile("a /a ", ""); + writeTrashFile("a /a ", ""); + writeTrashFile("a/a", ""); + writeTrashFile("a/a ", ""); + writeTrashFile("a/a ", ""); + + writeIgnoreFile(".gitignore", "a\\ ", "a \\ "); + + beginWalk(); + assertEntry(F, tracked, ".gitignore"); + assertEntry(D, ignored, "a "); + assertEntry(F, ignored, "a /a"); + assertEntry(F, ignored, "a /a "); + assertEntry(F, ignored, "a /a "); + assertEntry(D, ignored, "a "); + assertEntry(F, ignored, "a /a"); + assertEntry(F, ignored, "a /a "); + assertEntry(F, ignored, "a /a "); + assertEntry(D, tracked, "a"); + assertEntry(F, tracked, "a/a"); + assertEntry(F, ignored, "a/a "); + assertEntry(F, ignored, "a/a "); + endWalk(); + } + + @Test public void testToString() throws Exception { assertEquals(Arrays.asList("").toString(), new IgnoreNode().toString()); assertEquals(Arrays.asList("hello").toString(), @@ -411,4 +534,12 @@ public class IgnoreNodeTest extends RepositoryTestCase { data.append(line + "\n"); writeTrashFile(name, data.toString()); } + + private InputStream writeToString(String... rules) throws IOException { + StringBuilder data = new StringBuilder(); + for (String line : rules) { + data.append(line + "\n"); + } + return new ByteArrayInputStream(data.toString().getBytes("UTF-8")); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java index a7a78f841f..f8eb126826 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java @@ -830,9 +830,45 @@ public class IgnoreRuleSpecialCasesTest { } @Test + public void testIgnoredBackslash() throws Exception { + // In Git CLI a\b\c is equal to abc + assertMatch("a\\b\\c", "abc", true); + } + + @Test public void testEscapedBackslash() throws Exception { // In Git CLI a\\b matches a\b file assertMatch("a\\\\b", "a\\b", true); + assertMatch("a\\\\b\\c", "a\\bc", true); + + } + + @Test + public void testEscapedExclamationMark() throws Exception { + assertMatch("\\!b!.txt", "!b!.txt", true); + assertMatch("a\\!b!.txt", "a!b!.txt", true); + } + + @Test + public void testEscapedHash() throws Exception { + assertMatch("\\#b", "#b", true); + assertMatch("a\\#", "a#", true); + } + + @Test + public void testEscapedTrailingSpaces() throws Exception { + assertMatch("\\ ", " ", true); + assertMatch("a\\ ", "a ", true); + } + + @Test + public void testNotEscapingBackslash() throws Exception { + assertMatch("\\out", "out", true); + assertMatch("\\out", "a/out", true); + assertMatch("c:\\/", "c:/", true); + assertMatch("c:\\/", "a/c:/", true); + assertMatch("c:\\tmp", "c:tmp", true); + assertMatch("c:\\tmp", "a/c:tmp", true); } @Test @@ -841,6 +877,100 @@ public class IgnoreRuleSpecialCasesTest { } @Test + public void testBackslash() throws Exception { + assertMatch("a\\", "a", true); + assertMatch("\\a", "a", true); + assertMatch("a/\\", "a/", true); + assertMatch("a/b\\", "a/b", true); + assertMatch("\\a/b", "a/b", true); + assertMatch("/\\a", "/a", true); + assertMatch("\\a\\b\\c\\", "abc", true); + assertMatch("/\\a/\\b/\\c\\", "a/b/c", true); + + // empty path segment doesn't match + assertMatch("\\/a", "/a", false); + assertMatch("\\/a", "a", false); + } + + @Test + public void testDollar() throws Exception { + assertMatch("$", "$", true); + assertMatch("$x", "$x", true); + assertMatch("$x", "x$", false); + assertMatch("$x", "$", false); + + assertMatch("$x.*", "$x.a", true); + assertMatch("*$", "x$", true); + assertMatch("*.$", "x.$", true); + + assertMatch("$*x", "$ax", true); + assertMatch("x*$", "xa$", true); + assertMatch("x*$", "xa", false); + assertMatch("[a$b]", "$", true); + } + + @Test + public void testCaret() throws Exception { + assertMatch("^", "^", true); + assertMatch("^x", "^x", true); + assertMatch("^x", "x^", false); + assertMatch("^x", "^", false); + + assertMatch("^x.*", "^x.a", true); + assertMatch("*^", "x^", true); + assertMatch("*.^", "x.^", true); + + assertMatch("x*^", "xa^", true); + assertMatch("^*x", "^ax", true); + assertMatch("^*x", "ax", false); + assertMatch("[a^b]", "^", true); + } + + @Test + public void testPlus() throws Exception { + assertMatch("+", "+", true); + assertMatch("+x", "+x", true); + assertMatch("+x", "x+", false); + assertMatch("+x", "+", false); + assertMatch("x+", "xx", false); + + assertMatch("+x.*", "+x.a", true); + assertMatch("*+", "x+", true); + assertMatch("*.+", "x.+", true); + + assertMatch("x*+", "xa+", true); + assertMatch("+*x", "+ax", true); + assertMatch("+*x", "ax", false); + assertMatch("[a+b]", "+", true); + } + + @Test + public void testPipe() throws Exception { + assertMatch("|", "|", true); + assertMatch("|x", "|x", true); + assertMatch("|x", "x|", false); + assertMatch("|x", "|", false); + assertMatch("x|x", "xx", false); + + assertMatch("x|x.*", "x|x.a", true); + assertMatch("*|", "x|", true); + assertMatch("*.|", "x.|", true); + + assertMatch("x*|a", "xb|a", true); + assertMatch("b|*x", "b|ax", true); + assertMatch("b|*x", "ax", false); + assertMatch("[a|b]", "|", true); + } + + @Test + public void testBrackets() throws Exception { + assertMatch("{}*()", "{}x()", true); + assertMatch("[a{}()b][a{}()b]?[a{}()b][a{}()b]", "{}x()", true); + assertMatch("x*{x}3", "xa{x}3", true); + assertMatch("a*{x}3", "axxx", false); + } + + @Test public void testFilePathSimpleCase() throws Exception { assertFileNameMatch("a/b", "a/b", true); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java index 8dbe64478e..d66753da08 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java @@ -359,6 +359,33 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { } @Test + public void testFirstExactRef_IgnoresGarbageRef() throws IOException { + writeLooseRef("refs/heads/A", A); + write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "FAIL\n"); + + Ref a = refdir.firstExactRef("refs/heads/bad", "refs/heads/A"); + assertEquals("refs/heads/A", a.getName()); + assertEquals(A, a.getObjectId()); + } + + @Test + public void testExactRef_IgnoresGarbageRef() throws IOException { + writeLooseRef("refs/heads/A", A); + write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "FAIL\n"); + + Map<String, Ref> refs = + refdir.exactRef("refs/heads/bad", "refs/heads/A"); + + assertNull("no refs/heads/bad", refs.get("refs/heads/bad")); + + Ref a = refs.get("refs/heads/A"); + assertEquals("refs/heads/A", a.getName()); + assertEquals(A, a.getObjectId()); + + assertEquals(1, refs.size()); + } + + @Test public void testGetRefs_InvalidName() throws IOException { writeLooseRef("refs/heads/A", A); @@ -464,6 +491,21 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { } @Test + public void testFirstExactRef_Mixed() throws IOException { + writeLooseRef("refs/heads/A", A); + writePackedRef("refs/tags/v1.0", v1_0); + + Ref a = refdir.firstExactRef("refs/heads/A", "refs/tags/v1.0"); + Ref one = refdir.firstExactRef("refs/tags/v1.0", "refs/heads/A"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/tags/v1.0", one.getName()); + + assertEquals(A, a.getObjectId()); + assertEquals(v1_0, one.getObjectId()); + } + + @Test public void testGetRefs_TagsOnly_AllLoose() throws IOException { Map<String, Ref> tags; Ref a; @@ -983,6 +1025,25 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { } @Test + public void testExactRef_EmptyDatabase() throws IOException { + Ref r; + + r = refdir.exactRef(HEAD); + assertTrue(r.isSymbolic()); + assertSame(LOOSE, r.getStorage()); + assertEquals("refs/heads/master", r.getTarget().getName()); + assertSame(NEW, r.getTarget().getStorage()); + assertNull(r.getTarget().getObjectId()); + + assertNull(refdir.exactRef("refs/heads/master")); + assertNull(refdir.exactRef("refs/tags/v1.0")); + assertNull(refdir.exactRef("FETCH_HEAD")); + assertNull(refdir.exactRef("NOT.A.REF.NAME")); + assertNull(refdir.exactRef("master")); + assertNull(refdir.exactRef("v1.0")); + } + + @Test public void testGetRef_FetchHead() throws IOException { // This is an odd special case where we need to make sure we read // exactly the first 40 bytes of the file and nothing further on @@ -1000,6 +1061,23 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { } @Test + public void testExactRef_FetchHead() throws IOException { + // This is an odd special case where we need to make sure we read + // exactly the first 40 bytes of the file and nothing further on + // that line, or the remainder of the file. + write(new File(diskRepo.getDirectory(), "FETCH_HEAD"), A.name() + + "\tnot-for-merge" + + "\tbranch 'master' of git://egit.eclipse.org/jgit\n"); + + Ref r = refdir.exactRef("FETCH_HEAD"); + assertFalse(r.isSymbolic()); + assertEquals(A, r.getObjectId()); + assertEquals("FETCH_HEAD", r.getName()); + assertFalse(r.isPeeled()); + assertNull(r.getPeeledObjectId()); + } + + @Test public void testGetRef_AnyHeadWithGarbage() throws IOException { write(new File(diskRepo.getDirectory(), "refs/heads/A"), A.name() + "012345 . this is not a standard reference\n" diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java index db31fd34cd..6238a354d8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java @@ -51,7 +51,6 @@ package org.eclipse.jgit.lib; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -503,27 +502,6 @@ public class ConfigTest { } @Test - public void testEmptyString() throws ConfigInvalidException { - Config c = parse("[my]\n\tempty =\n"); - assertNull(c.getString("my", null, "empty")); - - String[] values = c.getStringList("my", null, "empty"); - assertNotNull(values); - assertEquals(1, values.length); - assertNull(values[0]); - - // always matches the default, because its non-boolean - assertTrue(c.getBoolean("my", "empty", true)); - assertFalse(c.getBoolean("my", "empty", false)); - - assertEquals("[my]\n\tempty =\n", c.toText()); - - c = new Config(); - c.setStringList("my", null, "empty", Arrays.asList(values)); - assertEquals("[my]\n\tempty =\n", c.toText()); - } - - @Test public void testUnsetBranchSection() throws ConfigInvalidException { Config c = parse("" // + "[branch \"keep\"]\n" @@ -699,6 +677,68 @@ public class ConfigTest { assertEquals("1", c.getString("a", null, "y")); } + @Test + public void testExplicitlySetEmptyString() throws Exception { + Config c = new Config(); + c.setString("a", null, "x", "0"); + c.setString("a", null, "y", ""); + + assertEquals("0", c.getString("a", null, "x")); + assertEquals(0, c.getInt("a", null, "x", 1)); + + assertEquals("", c.getString("a", null, "y")); + assertArrayEquals(new String[]{""}, c.getStringList("a", null, "y")); + try { + c.getInt("a", null, "y", 1); + } catch (IllegalArgumentException e) { + assertEquals("Invalid integer value: a.y=", e.getMessage()); + } + + assertNull(c.getString("a", null, "z")); + assertArrayEquals(new String[]{}, c.getStringList("a", null, "z")); + } + + @Test + public void testParsedEmptyString() throws Exception { + Config c = parse("[a]\n" + + "x = 0\n" + + "y =\n"); + + assertEquals("0", c.getString("a", null, "x")); + assertEquals(0, c.getInt("a", null, "x", 1)); + + assertNull(c.getString("a", null, "y")); + assertArrayEquals(new String[]{null}, c.getStringList("a", null, "y")); + try { + c.getInt("a", null, "y", 1); + } catch (IllegalArgumentException e) { + assertEquals("Invalid integer value: a.y=", e.getMessage()); + } + + assertNull(c.getString("a", null, "z")); + assertArrayEquals(new String[]{}, c.getStringList("a", null, "z")); + } + + @Test + public void testSetStringListWithEmptyValue() throws Exception { + Config c = new Config(); + c.setStringList("a", null, "x", Arrays.asList("")); + assertArrayEquals(new String[]{""}, c.getStringList("a", null, "x")); + } + + @Test + public void testEmptyValueAtEof() throws Exception { + String text = "[a]\nx ="; + Config c = parse(text); + assertNull(c.getString("a", null, "x")); + assertArrayEquals(new String[]{null}, + c.getStringList("a", null, "x")); + c = parse(text + "\n"); + assertNull(c.getString("a", null, "x")); + assertArrayEquals(new String[]{null}, + c.getStringList("a", null, "x")); + } + private static void assertReadLong(long exp) throws ConfigInvalidException { assertReadLong(exp, String.valueOf(exp)); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java index 274757d95d..3abe81cf85 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java @@ -1810,6 +1810,17 @@ public class ObjectCheckerTest { } @Test + public void testBug477090() throws CorruptObjectException { + checker.setSafeForMacOS(true); + final byte[] bytes = { + // U+221E 0xe2889e INFINITY ∞ + (byte) 0xe2, (byte) 0x88, (byte) 0x9e, + // .html + 0x2e, 0x68, 0x74, 0x6d, 0x6c }; + checker.checkPathSegment(bytes, 0, bytes.length); + } + + @Test public void testRejectDotAtEndOnWindows() { checker.setSafeForWindows(true); try { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java index abf57d6cd3..2198b87aa5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdTest.java @@ -49,6 +49,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import org.eclipse.jgit.errors.InvalidObjectIdException; + import org.junit.Test; public class ObjectIdTest { @@ -124,6 +126,21 @@ public class ObjectIdTest { assertEquals(x.toLowerCase(), oid.name()); } + @Test(expected = InvalidObjectIdException.class) + public void testFromString_short() { + ObjectId.fromString("cafe1234"); + } + + @Test(expected = InvalidObjectIdException.class) + public void testFromString_nonHex() { + ObjectId.fromString("0123456789abcdefghij0123456789abcdefghij"); + } + + @Test(expected = InvalidObjectIdException.class) + public void testFromString_shortNonHex() { + ObjectId.fromString("6789ghij"); + } + @Test public void testGetByte() { byte[] raw = new byte[20]; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java index f2ed684511..109f401898 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java @@ -84,6 +84,12 @@ public class RefTest extends SampleDataRepositoryTestCase { } } + private void writeNewRef(String name, ObjectId value) throws IOException { + RefUpdate updateRef = db.updateRef(name); + updateRef.setNewObjectId(value); + assertEquals(RefUpdate.Result.NEW, updateRef.update()); + } + @Test public void testRemoteNames() throws Exception { FileBasedConfig config = db.getConfig(); @@ -192,6 +198,50 @@ public class RefTest extends SampleDataRepositoryTestCase { assertEquals(Storage.LOOSE, ref.getStorage()); } + @Test + public void testGetShortRef() throws IOException { + Ref ref = db.getRef("master"); + assertEquals("refs/heads/master", ref.getName()); + assertEquals(db.resolve("refs/heads/master"), ref.getObjectId()); + } + + @Test + public void testGetShortExactRef() throws IOException { + assertNull(db.getRefDatabase().exactRef("master")); + + Ref ref = db.getRefDatabase().exactRef("HEAD"); + assertEquals("HEAD", ref.getName()); + assertEquals("refs/heads/master", ref.getTarget().getName()); + assertEquals(db.resolve("refs/heads/master"), ref.getObjectId()); + } + + @Test + public void testRefsUnderRefs() throws IOException { + ObjectId masterId = db.resolve("refs/heads/master"); + writeNewRef("refs/heads/refs/foo/bar", masterId); + + assertNull(db.getRefDatabase().exactRef("refs/foo/bar")); + + Ref ref = db.getRef("refs/foo/bar"); + assertEquals("refs/heads/refs/foo/bar", ref.getName()); + assertEquals(db.resolve("refs/heads/master"), ref.getObjectId()); + } + + @Test + public void testAmbiguousRefsUnderRefs() throws IOException { + ObjectId masterId = db.resolve("refs/heads/master"); + writeNewRef("refs/foo/bar", masterId); + writeNewRef("refs/heads/refs/foo/bar", masterId); + + Ref exactRef = db.getRefDatabase().exactRef("refs/foo/bar"); + assertEquals("refs/foo/bar", exactRef.getName()); + assertEquals(masterId, exactRef.getObjectId()); + + Ref ref = db.getRef("refs/foo/bar"); + assertEquals("refs/foo/bar", ref.getName()); + assertEquals(masterId, ref.getObjectId()); + } + /** * Let an "outsider" create a loose ref with the same name as a packed one * diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java index 0cab987e6a..6c6292558e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java @@ -43,11 +43,13 @@ package org.eclipse.jgit.lib; +import static org.hamcrest.CoreMatchers.hasItem; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -147,4 +149,28 @@ public class RepositoryCacheTest extends RepositoryTestCase { d2.close(); d2.close(); } + + @Test + public void testGetRegisteredWhenEmpty() { + assertEquals(0, RepositoryCache.getRegisteredKeys().size()); + } + + @Test + public void testGetRegistered() { + RepositoryCache.register(db); + + assertThat(RepositoryCache.getRegisteredKeys(), + hasItem(FileKey.exact(db.getDirectory(), db.getFS()))); + assertEquals(1, RepositoryCache.getRegisteredKeys().size()); + } + + @Test + public void testUnregister() { + RepositoryCache.register(db); + RepositoryCache + .unregister(FileKey.exact(db.getDirectory(), db.getFS())); + + assertEquals(0, RepositoryCache.getRegisteredKeys().size()); + } + } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java index 19e495b408..7ef6448e57 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason; import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; @@ -308,7 +309,7 @@ public class RecursiveMergerTest extends RepositoryTestCase { if (indexState != IndexState.Bare) assertEquals( "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5\n6\n7-res(side)\n8\n9-side\n]", - indexState(RepositoryTestCase.CONTENT)); + indexState(LocalDiskRepositoryTestCase.CONTENT)); if (worktreeState != WorktreeState.Bare && worktreeState != WorktreeState.Missing) assertEquals( @@ -393,7 +394,7 @@ public class RecursiveMergerTest extends RepositoryTestCase { if (indexState != IndexState.Bare) assertEquals( "[f, mode:100644, content:1-master-r\n2\n3-side-r\n]", - indexState(RepositoryTestCase.CONTENT)); + indexState(LocalDiskRepositoryTestCase.CONTENT)); if (worktreeState != WorktreeState.Bare && worktreeState != WorktreeState.Missing) assertEquals( @@ -478,7 +479,7 @@ public class RecursiveMergerTest extends RepositoryTestCase { if (indexState != IndexState.Bare) assertEquals( "[f, mode:100644, content:1\nx(side)\n2\n3\ny(side-again)\n]", - indexState(RepositoryTestCase.CONTENT)); + indexState(LocalDiskRepositoryTestCase.CONTENT)); if (worktreeState != WorktreeState.Bare && worktreeState != WorktreeState.Missing) assertEquals("1\nx(side)\n2\n3\ny(side-again)\n", read("f")); @@ -561,7 +562,7 @@ public class RecursiveMergerTest extends RepositoryTestCase { if (indexState != IndexState.Bare) assertEquals( "[f, mode:100644, content:1-master-r\n2\n3-side-r\n][m.c, mode:100644, content:0][m.m, mode:100644, content:1][s.c, mode:100644, content:0][s.m, mode:100644, content:1]", - indexState(RepositoryTestCase.CONTENT)); + indexState(LocalDiskRepositoryTestCase.CONTENT)); if (worktreeState != WorktreeState.Bare && worktreeState != WorktreeState.Missing) { assertEquals( @@ -638,7 +639,7 @@ public class RecursiveMergerTest extends RepositoryTestCase { "[f, mode:100644, stage:1, content:1-master\n2\n3\n4\n5\n6\n7\n8\n9-side\n]" + "[f, mode:100644, stage:2, content:1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n]" + "[f, mode:100644, stage:3, content:1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n]", - indexState(RepositoryTestCase.CONTENT)); + indexState(LocalDiskRepositoryTestCase.CONTENT)); assertEquals( "1-master\n2\n3\n4\n5\n6\n<<<<<<< OURS\n7-conflict\n=======\n7-res(side)\n>>>>>>> THEIRS\n8\n9-side\n", read("f")); @@ -736,7 +737,7 @@ public class RecursiveMergerTest extends RepositoryTestCase { if (indexState != IndexState.Bare) assertEquals( "[f, mode:100644, content:1-master\n2\n3-res(master)\n4\n5-other\n6\n7-res(side)\n8\n9-side\n]", - indexState(RepositoryTestCase.CONTENT)); + indexState(LocalDiskRepositoryTestCase.CONTENT)); if (worktreeState != WorktreeState.Bare && worktreeState != WorktreeState.Missing) assertEquals( diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java index 478a93b770..cd6a4bea2e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java @@ -227,6 +227,80 @@ public class ResolveMergerTest extends RepositoryTestCase { } /** + * A tracked file is replaced by a folder in THEIRS. + * + * @param strategy + * @throws Exception + */ + @Theory + public void checkFileReplacedByFolderInTheirs(MergeStrategy strategy) + throws Exception { + Git git = Git.wrap(db); + + writeTrashFile("sub", "file"); + git.add().addFilepattern("sub").call(); + RevCommit first = git.commit().setMessage("initial").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("side").call(); + + git.rm().addFilepattern("sub").call(); + writeTrashFile("sub/file", "subfile"); + git.add().addFilepattern("sub/file").call(); + RevCommit masterCommit = git.commit().setMessage("file -> folder") + .call(); + + git.checkout().setName("master").call(); + writeTrashFile("noop", "other"); + git.add().addFilepattern("noop").call(); + git.commit().setAll(true).setMessage("noop").call(); + + MergeResult mergeRes = git.merge().setStrategy(strategy) + .include(masterCommit).call(); + assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus()); + assertEquals( + "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]", + indexState(CONTENT)); + } + + /** + * A tracked file is replaced by a folder in OURS. + * + * @param strategy + * @throws Exception + */ + @Theory + public void checkFileReplacedByFolderInOurs(MergeStrategy strategy) + throws Exception { + Git git = Git.wrap(db); + + writeTrashFile("sub", "file"); + git.add().addFilepattern("sub").call(); + RevCommit first = git.commit().setMessage("initial").call(); + + git.checkout().setCreateBranch(true).setStartPoint(first) + .setName("side").call(); + writeTrashFile("noop", "other"); + git.add().addFilepattern("noop").call(); + RevCommit sideCommit = git.commit().setAll(true).setMessage("noop") + .call(); + + git.checkout().setName("master").call(); + git.rm().addFilepattern("sub").call(); + writeTrashFile("sub/file", "subfile"); + git.add().addFilepattern("sub/file").call(); + git.commit().setMessage("file -> folder") + .call(); + + MergeResult mergeRes = git.merge().setStrategy(strategy) + .include(sideCommit).call(); + assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus()); + assertEquals( + "[noop, mode:100644, content:other][sub/file, mode:100644, content:subfile]", + indexState(CONTENT)); + } + + /** * An existing directory without tracked content should not prevent merging * a file with that name. * diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java index 22e55fe729..2b46498470 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleInitTest.java @@ -246,9 +246,8 @@ public class SubmoduleInitTest extends RepositoryTestCase { if (File.separatorChar == '\\') base = base.replace('\\', '/'); FileBasedConfig config = db.getConfig(); - config.setString(ConfigConstants.CONFIG_REMOTE_SECTION, - Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL, - null); + config.unset(ConfigConstants.CONFIG_REMOTE_SECTION, + Constants.DEFAULT_REMOTE_NAME, ConfigConstants.CONFIG_KEY_URL); config.save(); SubmoduleWalk generator = SubmoduleWalk.forIndex(db); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java index f7acaa7880..72b4611df5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java @@ -65,6 +65,7 @@ import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Config; @@ -101,6 +102,13 @@ public class SubmoduleWalkTest extends RepositoryTestCase { } @Test + public void bareRepositoryWithNoSubmodules() throws IOException { + FileRepository bareRepo = createBareRepository(); + boolean result = SubmoduleWalk.containsGitModulesFile(bareRepo); + assertFalse(result); + } + + @Test public void repositoryWithRootLevelSubmodule() throws IOException, ConfigInvalidException, NoWorkTreeException, GitAPIException { final ObjectId id = ObjectId diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java new file mode 100644 index 0000000000..7578c6e3eb --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BaseReceivePackTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015, Google Inc. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.lib.ObjectId; +import org.junit.Test; + +/** Tests for base receive-pack utilities. */ +public class BaseReceivePackTest { + @Test + public void parseCommand() throws Exception { + String o = "0000000000000000000000000000000000000000"; + String n = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + String r = "refs/heads/master"; + ReceiveCommand cmd = BaseReceivePack.parseCommand(o + " " + n + " " + r); + assertEquals(ObjectId.zeroId(), cmd.getOldId()); + assertEquals("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + cmd.getNewId().name()); + assertEquals("refs/heads/master", cmd.getRefName()); + + assertParseCommandFails(null); + assertParseCommandFails(""); + assertParseCommandFails(o.substring(35) + " " + n.substring(35) + + " " + r + "\n"); + assertParseCommandFails(o + " " + n + " " + r + "\n"); + assertParseCommandFails(o + " " + n + " " + "refs^foo"); + assertParseCommandFails(o + " " + n.substring(10) + " " + r); + assertParseCommandFails(o.substring(10) + " " + n + " " + r); + assertParseCommandFails("X" + o.substring(1) + " " + n + " " + r); + assertParseCommandFails(o + " " + "X" + n.substring(1) + " " + r); + } + + private void assertParseCommandFails(String input) { + try { + BaseReceivePack.parseCommand(input); + fail(); + } catch (PackProtocolException e) { + // Expected. + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java index 24cee0a6a1..ba89d2d616 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java @@ -147,6 +147,18 @@ public class BundleWriterTest extends SampleDataRepositoryTestCase { } } + @Test + public void testAbortWrite() throws Exception { + boolean caught = false; + try { + makeBundleWithCallback( + "refs/heads/aa", db.resolve("a").name(), null, false); + } catch (WriteAbortedException e) { + caught = true; + } + assertTrue(caught); + } + private static FetchResult fetchFromBundle(final Repository newRepo, final byte[] bundle) throws URISyntaxException, NotSupportedException, TransportException { @@ -161,9 +173,17 @@ public class BundleWriterTest extends SampleDataRepositoryTestCase { private byte[] makeBundle(final String name, final String anObjectToInclude, final RevCommit assume) throws FileNotFoundException, IOException { + return makeBundleWithCallback(name, anObjectToInclude, assume, true); + } + + private byte[] makeBundleWithCallback(final String name, + final String anObjectToInclude, final RevCommit assume, + boolean value) + throws FileNotFoundException, IOException { final BundleWriter bw; bw = new BundleWriter(db); + bw.setObjectCountCallback(new NaiveObjectCountCallback(value)); bw.include(name, ObjectId.fromString(anObjectToInclude)); if (assume != null) bw.assume(assume); @@ -172,4 +192,19 @@ public class BundleWriterTest extends SampleDataRepositoryTestCase { return out.toByteArray(); } + private static class NaiveObjectCountCallback + implements ObjectCountCallback { + private final boolean value; + + NaiveObjectCountCallback(boolean value) { + this.value = value; + } + + @Override + public void setObjectCount(long unused) throws WriteAbortedException { + if (!value) + throw new WriteAbortedException(); + } + } + } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HMACSHA1NonceGeneratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HMACSHA1NonceGeneratorTest.java new file mode 100644 index 0000000000..1e79b7a3b0 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HMACSHA1NonceGeneratorTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015, Google Inc. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.PushCertificate.NonceStatus; +import org.junit.Before; +import org.junit.Test; + +/** Test for HMAC SHA-1 certificate verifier. */ +public class HMACSHA1NonceGeneratorTest { + private static final long TS = 1433954361; + + private HMACSHA1NonceGenerator gen; + private Repository db; + + @Before + public void setUp() { + gen = new HMACSHA1NonceGenerator("sekret"); + db = new InMemoryRepository(new DfsRepositoryDescription("db")); + } + + @Test + public void missing() throws Exception { + assertEquals(NonceStatus.MISSING, gen.verify("", "1234", db, false, 0)); + } + + @Test + public void unsolicited() throws Exception { + assertEquals(NonceStatus.UNSOLICITED, gen.verify("1234", "", db, false, 0)); + } + + @Test + public void invalidFormat() throws Exception { + String sent = gen.createNonce(db, TS); + int idx = sent.indexOf('-'); + String sig = sent.substring(idx, sent.length() - idx); + assertEquals(NonceStatus.BAD, + gen.verify(Long.toString(TS), sent, db, true, 100)); + assertEquals(NonceStatus.BAD, gen.verify(sig, sent, db, true, 100)); + assertEquals(NonceStatus.BAD, gen.verify("xxx-" + sig, sent, db, true, 100)); + assertEquals(NonceStatus.BAD, gen.verify(sent, "xxx-" + sig, db, true, 100)); + } + + @Test + public void slop() throws Exception { + String sent = gen.createNonce(db, TS - 10); + String received = gen.createNonce(db, TS); + assertEquals(NonceStatus.BAD, + gen.verify(received, sent, db, false, 0)); + assertEquals(NonceStatus.BAD, + gen.verify(received, sent, db, false, 11)); + assertEquals(NonceStatus.SLOP, + gen.verify(received, sent, db, true, 0)); + assertEquals(NonceStatus.SLOP, + gen.verify(received, sent, db, true, 9)); + assertEquals(NonceStatus.OK, + gen.verify(received, sent, db, true, 10)); + assertEquals(NonceStatus.OK, + gen.verify(received, sent, db, true, 11)); + } + + @Test + public void ok() throws Exception { + String sent = gen.createNonce(db, TS); + assertEquals(NonceStatus.OK, gen.verify(sent, sent, db, false, 0)); + } + + @Test + public void signedByDifferentKey() throws Exception { + HMACSHA1NonceGenerator other = new HMACSHA1NonceGenerator("other"); + String sent = gen.createNonce(db, TS); + String received = other.createNonce(db, TS); + assertNotEquals(received, sent); + assertEquals(NonceStatus.BAD, + gen.verify(received, sent, db, false, 0)); + } + + @Test + public void signedByDifferentKeyWithSlop() throws Exception { + HMACSHA1NonceGenerator other = new HMACSHA1NonceGenerator("other"); + String sent = gen.createNonce(db, TS - 10); + String received = other.createNonce(db, TS); + assertEquals(NonceStatus.BAD, gen.verify(received, sent, db, true, 100)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateIdentTest.java new file mode 100644 index 0000000000..68aff72df4 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateIdentTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static org.eclipse.jgit.transport.PushCertificateIdent.parse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Date; +import java.util.TimeZone; + +import org.eclipse.jgit.lib.PersonIdent; +import org.junit.Test; + +public class PushCertificateIdentTest { + @Test + public void parseValid() throws Exception { + String raw = "A U. Thor <a_u_thor@example.com> 1218123387 +0700"; + PushCertificateIdent ident = parse(raw); + assertEquals(raw, ident.getRaw()); + assertEquals("A U. Thor <a_u_thor@example.com>", ident.getUserId()); + assertEquals("A U. Thor", ident.getName()); + assertEquals("a_u_thor@example.com", ident.getEmailAddress()); + assertEquals(1218123387000L, ident.getWhen().getTime()); + assertEquals(TimeZone.getTimeZone("GMT+0700"), ident.getTimeZone()); + assertEquals(7 * 60, ident.getTimeZoneOffset()); + } + + @Test + public void trimName() throws Exception { + String name = "A U. Thor"; + String email = "a_u_thor@example.com"; + String rest = "<a_u_thor@example.com> 1218123387 +0700"; + + checkNameEmail(name, email, name + rest); + checkNameEmail(name, email, " " + name + rest); + checkNameEmail(name, email, " " + name + rest); + checkNameEmail(name, email, name + " " + rest); + checkNameEmail(name, email, name + " " + rest); + checkNameEmail(name, email, " " + name + " " + rest); + } + + @Test + public void noEmail() throws Exception { + String name = "A U. Thor"; + String rest = " 1218123387 +0700"; + + checkNameEmail(name, null, name + rest); + checkNameEmail(name, null, " " + name + rest); + checkNameEmail(name, null, " " + name + rest); + checkNameEmail(name, null, name + " " + rest); + checkNameEmail(name, null, name + " " + rest); + checkNameEmail(name, null, " " + name + " " + rest); + } + + @Test + public void exoticUserId() throws Exception { + String rest = " 218123387 +0700"; + assertEquals("", parse(rest).getUserId()); + + String id = "foo\n\0bar\uabcd\n "; + assertEquals(id, parse(id + rest).getUserId()); + } + + @Test + public void fuzzyCasesMatchPersonIdent() throws Exception { + // See RawParseUtils_ParsePersonIdentTest#testParsePersonIdent_fuzzyCases() + Date when = new Date(1234567890000l); + TimeZone tz = TimeZone.getTimeZone("GMT-7"); + + assertMatchesPersonIdent( + "A U Thor <author@example.com>, C O. Miter <comiter@example.com> 1234567890 -0700", + new PersonIdent("A U Thor", "author@example.com", when, tz), + "A U Thor <author@example.com>, C O. Miter <comiter@example.com>"); + assertMatchesPersonIdent( + "A U Thor <author@example.com> and others 1234567890 -0700", + new PersonIdent("A U Thor", "author@example.com", when, tz), + "A U Thor <author@example.com> and others"); + } + + @Test + public void incompleteCasesMatchPersonIdent() throws Exception { + // See RawParseUtils_ParsePersonIdentTest#testParsePersonIdent_incompleteCases() + Date when = new Date(1234567890000l); + TimeZone tz = TimeZone.getTimeZone("GMT-7"); + + assertMatchesPersonIdent( + "Me <> 1234567890 -0700", + new PersonIdent("Me", "", when, tz), + "Me <>"); + assertMatchesPersonIdent( + " <me@example.com> 1234567890 -0700", + new PersonIdent("", "me@example.com", when, tz), + " <me@example.com>"); + assertMatchesPersonIdent( + " <> 1234567890 -0700", + new PersonIdent("", "", when, tz), + " <>"); + assertMatchesPersonIdent( + "<>", + new PersonIdent("", "", 0, 0), + "<>"); + assertMatchesPersonIdent( + " <>", + new PersonIdent("", "", 0, 0), + " <>"); + assertMatchesPersonIdent( + "<me@example.com>", + new PersonIdent("", "me@example.com", 0, 0), + "<me@example.com>"); + assertMatchesPersonIdent( + " <me@example.com>", + new PersonIdent("", "me@example.com", 0, 0), + " <me@example.com>"); + assertMatchesPersonIdent( + "Me <>", + new PersonIdent("Me", "", 0, 0), + "Me <>"); + assertMatchesPersonIdent( + "Me <me@example.com>", + new PersonIdent("Me", "me@example.com", 0, 0), + "Me <me@example.com>"); + assertMatchesPersonIdent( + "Me <me@example.com> 1234567890", + new PersonIdent("Me", "me@example.com", 0, 0), + "Me <me@example.com>"); + assertMatchesPersonIdent( + "Me <me@example.com> 1234567890 ", + new PersonIdent("Me", "me@example.com", 0, 0), + "Me <me@example.com>"); + } + + private static void assertMatchesPersonIdent(String raw, + PersonIdent expectedPersonIdent, String expectedUserId) { + PushCertificateIdent certIdent = PushCertificateIdent.parse(raw); + assertNotNull(raw); + assertEquals(raw, certIdent.getRaw()); + assertEquals(expectedPersonIdent.getName(), certIdent.getName()); + assertEquals(expectedPersonIdent.getEmailAddress(), + certIdent.getEmailAddress()); + assertEquals(expectedPersonIdent.getWhen(), certIdent.getWhen()); + assertEquals(expectedPersonIdent.getTimeZoneOffset(), + certIdent.getTimeZoneOffset()); + assertEquals(expectedUserId, certIdent.getUserId()); + } + + private static void checkNameEmail(String expectedName, String expectedEmail, + String raw) { + PushCertificateIdent ident = parse(raw); + assertNotNull(ident); + assertEquals(raw, ident.getRaw()); + assertEquals(expectedName, ident.getName()); + assertEquals(expectedEmail, ident.getEmailAddress()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java new file mode 100644 index 0000000000..0647167eab --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2015, Google Inc. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.PushCertificate.NonceStatus; +import org.junit.Before; +import org.junit.Test; + +/** Test for push certificate parsing. */ +public class PushCertificateParserTest { + // Example push certificate generated by C git 2.2.0. + private static final String INPUT = "001ccertificate version 0.1\n" + + "0041pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n" + + "0024pushee git://localhost/repo.git\n" + + "002anonce 1433954361-bde756572d665bba81d8\n" + + "0005\n" + + "00680000000000000000000000000000000000000000" + + " 6c2b981a177396fb47345b7df3e4d3f854c6bea7" + + " refs/heads/master\n" + + "0022-----BEGIN PGP SIGNATURE-----\n" + + "0016Version: GnuPG v1\n" + + "0005\n" + + "0045iQEcBAABAgAGBQJVeGg5AAoJEPfTicJkUdPkUggH/RKAeI9/i/LduuiqrL/SSdIa\n" + + "00459tYaSqJKLbXz63M/AW4Sp+4u+dVCQvnAt/a35CVEnpZz6hN4Kn/tiswOWVJf4CO7\n" + + "0045htNubGs5ZMwvD6sLYqKAnrM3WxV/2TbbjzjZW6Jkidz3jz/WRT4SmjGYiEO7aA+V\n" + + "00454ZdIS9f7sW5VsHHYlNThCA7vH8Uu48bUovFXyQlPTX0pToSgrWV3JnTxDNxfn3iG\n" + + "0045IL0zTY/qwVCdXgFownLcs6J050xrrBWIKqfcWr3u4D2aCLyR0v+S/KArr7ulZygY\n" + + "0045+SOklImn8TAZiNxhWtA6ens66IiammUkZYFv7SSzoPLFZT4dC84SmGPWgf94NoQ=\n" + + "000a=XFeC\n" + + "0020-----END PGP SIGNATURE-----\n" + + "0012push-cert-end\n"; + + // Same push certificate, with all trailing newlines stripped. + // (Note that the canonical signed payload is the same, so the same signature + // is still valid.) + private static final String INPUT_NO_NEWLINES = "001bcertificate version 0.1" + + "0040pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700" + + "0023pushee git://localhost/repo.git" + + "0029nonce 1433954361-bde756572d665bba81d8" + + "0004" + + "00670000000000000000000000000000000000000000" + + " 6c2b981a177396fb47345b7df3e4d3f854c6bea7" + + " refs/heads/master" + + "0021-----BEGIN PGP SIGNATURE-----" + + "0015Version: GnuPG v1" + + "0004" + + "0044iQEcBAABAgAGBQJVeGg5AAoJEPfTicJkUdPkUggH/RKAeI9/i/LduuiqrL/SSdIa" + + "00449tYaSqJKLbXz63M/AW4Sp+4u+dVCQvnAt/a35CVEnpZz6hN4Kn/tiswOWVJf4CO7" + + "0044htNubGs5ZMwvD6sLYqKAnrM3WxV/2TbbjzjZW6Jkidz3jz/WRT4SmjGYiEO7aA+V" + + "00444ZdIS9f7sW5VsHHYlNThCA7vH8Uu48bUovFXyQlPTX0pToSgrWV3JnTxDNxfn3iG" + + "0044IL0zTY/qwVCdXgFownLcs6J050xrrBWIKqfcWr3u4D2aCLyR0v+S/KArr7ulZygY" + + "0044+SOklImn8TAZiNxhWtA6ens66IiammUkZYFv7SSzoPLFZT4dC84SmGPWgf94NoQ=" + + "0009=XFeC" + + "001f-----END PGP SIGNATURE-----" + + "0011push-cert-end"; + + private Repository db; + + @Before + public void setUp() { + db = new InMemoryRepository(new DfsRepositoryDescription("repo")); + } + + private static SignedPushConfig newEnabledConfig() { + Config cfg = new Config(); + cfg.setString("receive", null, "certnonceseed", "sekret"); + return SignedPushConfig.KEY.parse(cfg); + } + + private static SignedPushConfig newDisabledConfig() { + return SignedPushConfig.KEY.parse(new Config()); + } + + @Test + public void noCert() throws Exception { + PushCertificateParser parser = + new PushCertificateParser(db, newEnabledConfig()); + assertTrue(parser.enabled()); + assertNull(parser.build()); + + ObjectId oldId = ObjectId.zeroId(); + ObjectId newId = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + String line = oldId.name() + " " + newId.name() + " refs/heads/master"; + ReceiveCommand cmd = BaseReceivePack.parseCommand(line); + + parser.addCommand(cmd); + parser.addCommand(line); + assertNull(parser.build()); + } + + @Test + public void disabled() throws Exception { + PacketLineIn pckIn = newPacketLineIn(INPUT); + PushCertificateParser parser = + new PushCertificateParser(db, newDisabledConfig()); + assertFalse(parser.enabled()); + assertNull(parser.build()); + + parser.receiveHeader(pckIn, false); + parser.addCommand(pckIn.readString()); + assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString()); + parser.receiveSignature(pckIn); + assertNull(parser.build()); + } + + @Test + public void disabledParserStillRequiresCorrectSyntax() throws Exception { + PacketLineIn pckIn = newPacketLineIn("001ccertificate version XYZ\n"); + PushCertificateParser parser = + new PushCertificateParser(db, newDisabledConfig()); + assertFalse(parser.enabled()); + try { + parser.receiveHeader(pckIn, false); + fail("Expected PackProtocolException"); + } catch (PackProtocolException e) { + assertEquals( + "Push certificate has missing or invalid value for certificate" + + " version: XYZ", + e.getMessage()); + } + assertNull(parser.build()); + } + + @Test + public void parseCertFromPktLine() throws Exception { + PacketLineIn pckIn = newPacketLineIn(INPUT); + PushCertificateParser parser = + new PushCertificateParser(db, newEnabledConfig()); + parser.receiveHeader(pckIn, false); + parser.addCommand(pckIn.readString()); + assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString()); + parser.receiveSignature(pckIn); + + PushCertificate cert = parser.build(); + assertEquals("0.1", cert.getVersion()); + assertEquals("Dave Borowitz", cert.getPusherIdent().getName()); + assertEquals("dborowitz@google.com", + cert.getPusherIdent().getEmailAddress()); + assertEquals(1433954361000L, cert.getPusherIdent().getWhen().getTime()); + assertEquals(-7 * 60, cert.getPusherIdent().getTimeZoneOffset()); + assertEquals("git://localhost/repo.git", cert.getPushee()); + assertEquals("1433954361-bde756572d665bba81d8", cert.getNonce()); + + assertNotEquals(cert.getNonce(), parser.getAdvertiseNonce()); + assertEquals(PushCertificate.NonceStatus.BAD, cert.getNonceStatus()); + + assertEquals(1, cert.getCommands().size()); + ReceiveCommand cmd = cert.getCommands().get(0); + assertEquals("refs/heads/master", cmd.getRefName()); + assertEquals(ObjectId.zeroId(), cmd.getOldId()); + assertEquals("6c2b981a177396fb47345b7df3e4d3f854c6bea7", + cmd.getNewId().name()); + + assertEquals(concatPacketLines(INPUT, 0, 6), cert.toText()); + assertEquals(concatPacketLines(INPUT, 0, 17), cert.toTextWithSignature()); + + String signature = concatPacketLines(INPUT, 6, 17); + assertTrue(signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE)); + assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE + "\n")); + assertEquals(signature, cert.getSignature()); + } + + @Test + public void parseCertFromPktLineNoNewlines() throws Exception { + PacketLineIn pckIn = newPacketLineIn(INPUT_NO_NEWLINES); + PushCertificateParser parser = + new PushCertificateParser(db, newEnabledConfig()); + parser.receiveHeader(pckIn, false); + parser.addCommand(pckIn.readString()); + assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString()); + parser.receiveSignature(pckIn); + + PushCertificate cert = parser.build(); + assertEquals("0.1", cert.getVersion()); + assertEquals("Dave Borowitz", cert.getPusherIdent().getName()); + assertEquals("dborowitz@google.com", + cert.getPusherIdent().getEmailAddress()); + assertEquals(1433954361000L, cert.getPusherIdent().getWhen().getTime()); + assertEquals(-7 * 60, cert.getPusherIdent().getTimeZoneOffset()); + assertEquals("git://localhost/repo.git", cert.getPushee()); + assertEquals("1433954361-bde756572d665bba81d8", cert.getNonce()); + + assertNotEquals(cert.getNonce(), parser.getAdvertiseNonce()); + assertEquals(PushCertificate.NonceStatus.BAD, cert.getNonceStatus()); + + assertEquals(1, cert.getCommands().size()); + ReceiveCommand cmd = cert.getCommands().get(0); + assertEquals("refs/heads/master", cmd.getRefName()); + assertEquals(ObjectId.zeroId(), cmd.getOldId()); + assertEquals("6c2b981a177396fb47345b7df3e4d3f854c6bea7", + cmd.getNewId().name()); + + // Canonical signed payload has reinserted newlines. + assertEquals(concatPacketLines(INPUT, 0, 6), cert.toText()); + + String signature = concatPacketLines(INPUT, 6, 17); + assertTrue(signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE)); + assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE + "\n")); + assertEquals(signature, cert.getSignature()); + } + + @Test + public void testConcatPacketLines() throws Exception { + String input = "000bline 1\n000bline 2\n000bline 3\n"; + assertEquals("line 1\n", concatPacketLines(input, 0, 1)); + assertEquals("line 1\nline 2\n", concatPacketLines(input, 0, 2)); + assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 3)); + assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4)); + } + + @Test + public void testConcatPacketLinesInsertsNewlines() throws Exception { + String input = "000bline 1\n000aline 2000bline 3\n"; + assertEquals("line 1\n", concatPacketLines(input, 0, 1)); + assertEquals("line 1\nline 2\n", concatPacketLines(input, 0, 2)); + assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 3)); + assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4)); + } + + @Test + public void testParseReader() throws Exception { + Reader reader = new StringReader(concatPacketLines(INPUT, 0, 18)); + PushCertificate streamCert = PushCertificateParser.fromReader(reader); + + PacketLineIn pckIn = newPacketLineIn(INPUT); + PushCertificateParser pckParser = + new PushCertificateParser(db, newEnabledConfig()); + pckParser.receiveHeader(pckIn, false); + pckParser.addCommand(pckIn.readString()); + assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString()); + pckParser.receiveSignature(pckIn); + PushCertificate pckCert = pckParser.build(); + + // Nonce status is unsolicited since this was not parsed in the context of + // the wire protocol; as a result, certs are not actually equal. + assertEquals(NonceStatus.UNSOLICITED, streamCert.getNonceStatus()); + + assertEquals(pckCert.getVersion(), streamCert.getVersion()); + assertEquals(pckCert.getPusherIdent().getName(), + streamCert.getPusherIdent().getName()); + assertEquals(pckCert.getPusherIdent().getEmailAddress(), + streamCert.getPusherIdent().getEmailAddress()); + assertEquals(pckCert.getPusherIdent().getWhen().getTime(), + streamCert.getPusherIdent().getWhen().getTime()); + assertEquals(pckCert.getPusherIdent().getTimeZoneOffset(), + streamCert.getPusherIdent().getTimeZoneOffset()); + assertEquals(pckCert.getPushee(), streamCert.getPushee()); + assertEquals(pckCert.getNonce(), streamCert.getNonce()); + assertEquals(pckCert.getSignature(), streamCert.getSignature()); + assertEquals(pckCert.toText(), streamCert.toText()); + + assertEquals(pckCert.getCommands().size(), streamCert.getCommands().size()); + ReceiveCommand pckCmd = pckCert.getCommands().get(0); + ReceiveCommand streamCmd = streamCert.getCommands().get(0); + assertEquals(pckCmd.getRefName(), streamCmd.getRefName()); + assertEquals(pckCmd.getOldId(), streamCmd.getOldId()); + assertEquals(pckCmd.getNewId().name(), streamCmd.getNewId().name()); + } + + @Test + public void testParseString() throws Exception { + String str = concatPacketLines(INPUT, 0, 18); + assertEquals( + PushCertificateParser.fromReader(new StringReader(str)), + PushCertificateParser.fromString(str)); + } + + @Test + public void testParseMultipleFromStream() throws Exception { + String input = concatPacketLines(INPUT, 0, 17); + assertFalse(input.contains(PushCertificateParser.END_CERT)); + input += input; + Reader reader = new InputStreamReader( + new ByteArrayInputStream(Constants.encode(input))); + + assertNotNull(PushCertificateParser.fromReader(reader)); + assertNotNull(PushCertificateParser.fromReader(reader)); + assertEquals(-1, reader.read()); + assertNull(PushCertificateParser.fromReader(reader)); + } + + @Test + public void testMissingPusheeField() throws Exception { + // Omit pushee line from existing cert. (This means the signature would not + // match, but we're not verifying it here.) + String input = INPUT.replace("0024pushee git://localhost/repo.git\n", ""); + assertFalse(input.contains(PushCertificateParser.PUSHEE)); + + PacketLineIn pckIn = newPacketLineIn(input); + PushCertificateParser parser = + new PushCertificateParser(db, newEnabledConfig()); + parser.receiveHeader(pckIn, false); + parser.addCommand(pckIn.readString()); + assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString()); + parser.receiveSignature(pckIn); + + PushCertificate cert = parser.build(); + assertEquals("0.1", cert.getVersion()); + assertNull(cert.getPushee()); + assertFalse(cert.toText().contains(PushCertificateParser.PUSHEE)); + } + + private static String concatPacketLines(String input, int begin, int end) + throws IOException { + StringBuilder result = new StringBuilder(); + int i = 0; + PacketLineIn pckIn = newPacketLineIn(input); + while (i < end) { + String line; + try { + line = pckIn.readString(); + } catch (EOFException e) { + break; + } + if (++i > begin) { + result.append(line).append('\n'); + } + } + return result.toString(); + } + + private static PacketLineIn newPacketLineIn(String input) { + return new PacketLineIn(new ByteArrayInputStream(Constants.encode(input))); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java new file mode 100644 index 0000000000..68e0129525 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static org.eclipse.jgit.lib.ObjectId.zeroId; +import static org.eclipse.jgit.lib.RefUpdate.Result.FAST_FORWARD; +import static org.eclipse.jgit.lib.RefUpdate.Result.LOCK_FAILURE; +import static org.eclipse.jgit.lib.RefUpdate.Result.NEW; +import static org.eclipse.jgit.lib.RefUpdate.Result.NO_CHANGE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.junit.Before; +import org.junit.Test; + +public class PushCertificateStoreTest { + private static final ObjectId ID1 = + ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + private static final ObjectId ID2 = + ObjectId.fromString("badc0ffebadc0ffebadc0ffebadc0ffebadc0ffe"); + + private static PushCertificate newCert(String... updateLines) { + StringBuilder cert = new StringBuilder( + "certificate version 0.1\n" + + "pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n" + + "pushee git://localhost/repo.git\n" + + "nonce 1433954361-bde756572d665bba81d8\n" + + "\n"); + for (String updateLine : updateLines) { + cert.append(updateLine).append('\n'); + } + cert.append( + "-----BEGIN PGP SIGNATURE-----\n" + + "DUMMY/SIGNATURE\n" + + "-----END PGP SIGNATURE-----\n"); + try { + return PushCertificateParser.fromReader(new InputStreamReader( + new ByteArrayInputStream(Constants.encode(cert.toString())))); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + private static String command(ObjectId oldId, ObjectId newId, String ref) { + return oldId.name() + " " + newId.name() + " " + ref; + } + + private AtomicInteger ts = new AtomicInteger(1433954361); + private InMemoryRepository repo; + private PushCertificateStore store; + + @Before + public void setUp() throws Exception { + repo = new InMemoryRepository(new DfsRepositoryDescription("repo")); + store = newStore(); + } + + @Test + public void missingRef() throws Exception { + assertCerts("refs/heads/master"); + } + + @Test + public void saveNoChange() throws Exception { + assertEquals(NO_CHANGE, store.save()); + } + + @Test + public void saveOneCertOnOneRef() throws Exception { + PersonIdent ident = newIdent(); + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, ident); + assertEquals(NEW, store.save()); + assertCerts("refs/heads/master", addMaster); + assertCerts("refs/heads/branch"); + + try (RevWalk rw = new RevWalk(repo)) { + RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME)); + rw.parseBody(c); + assertEquals("Store push certificate for refs/heads/master\n", + c.getFullMessage()); + assertEquals(ident, c.getAuthorIdent()); + assertEquals(ident, c.getCommitterIdent()); + } + } + + @Test + public void saveTwoCertsOnSameRefInTwoUpdates() throws Exception { + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, newIdent()); + assertEquals(NEW, store.save()); + PushCertificate updateMaster = newCert( + command(ID1, ID2, "refs/heads/master")); + store.put(updateMaster, newIdent()); + assertEquals(FAST_FORWARD, store.save()); + assertCerts("refs/heads/master", updateMaster, addMaster); + } + + @Test + public void saveTwoCertsOnSameRefInOneUpdate() throws Exception { + PersonIdent ident1 = newIdent(); + PersonIdent ident2 = newIdent(); + PushCertificate updateMaster = newCert( + command(ID1, ID2, "refs/heads/master")); + store.put(updateMaster, ident2); + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, ident1); + assertEquals(NEW, store.save()); + assertCerts("refs/heads/master", updateMaster, addMaster); + } + + @Test + public void saveTwoCertsOnDifferentRefsInOneUpdate() throws Exception { + PersonIdent ident1 = newIdent(); + PersonIdent ident3 = newIdent(); + PushCertificate addBranch = newCert( + command(zeroId(), ID1, "refs/heads/branch")); + store.put(addBranch, ident3); + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, ident1); + assertEquals(NEW, store.save()); + assertCerts("refs/heads/master", addMaster); + assertCerts("refs/heads/branch", addBranch); + } + + @Test + public void saveTwoCertsOnDifferentRefsInTwoUpdates() throws Exception { + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, newIdent()); + assertEquals(NEW, store.save()); + PushCertificate addBranch = newCert( + command(zeroId(), ID1, "refs/heads/branch")); + store.put(addBranch, newIdent()); + assertEquals(FAST_FORWARD, store.save()); + assertCerts("refs/heads/master", addMaster); + assertCerts("refs/heads/branch", addBranch); + } + + @Test + public void saveOneCertOnMultipleRefs() throws Exception { + PersonIdent ident = newIdent(); + PushCertificate addMasterAndBranch = newCert( + command(zeroId(), ID1, "refs/heads/branch"), + command(zeroId(), ID2, "refs/heads/master")); + store.put(addMasterAndBranch, ident); + assertEquals(NEW, store.save()); + assertCerts("refs/heads/master", addMasterAndBranch); + assertCerts("refs/heads/branch", addMasterAndBranch); + + try (RevWalk rw = new RevWalk(repo)) { + RevCommit c = rw.parseCommit(repo.resolve(PushCertificateStore.REF_NAME)); + rw.parseBody(c); + assertEquals("Store push certificate for 2 refs\n", c.getFullMessage()); + assertEquals(ident, c.getAuthorIdent()); + assertEquals(ident, c.getCommitterIdent()); + } + } + + @Test + public void changeRefFileToDirectory() throws Exception { + PushCertificate deleteRefsHeads = newCert( + command(ID1, zeroId(), "refs/heads")); + store.put(deleteRefsHeads, newIdent()); + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, newIdent()); + assertEquals(NEW, store.save()); + assertCerts("refs/heads", deleteRefsHeads); + assertCerts("refs/heads/master", addMaster); + } + + @Test + public void getBeforeSaveDoesNotIncludePending() throws Exception { + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, newIdent()); + assertEquals(NEW, store.save()); + + PushCertificate updateMaster = newCert( + command(ID1, ID2, "refs/heads/master")); + store.put(updateMaster, newIdent()); + + assertCerts("refs/heads/master", addMaster); + assertEquals(FAST_FORWARD, store.save()); + assertCerts("refs/heads/master", updateMaster, addMaster); + } + + @Test + public void lockFailure() throws Exception { + PushCertificateStore store1 = store; + PushCertificateStore store2 = newStore(); + store2.get("refs/heads/master"); + + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store1.put(addMaster, newIdent()); + assertEquals(NEW, store1.save()); + + PushCertificate addBranch = newCert( + command(zeroId(), ID2, "refs/heads/branch")); + store2.put(addBranch, newIdent()); + + assertEquals(LOCK_FAILURE, store2.save()); + // Reread ref after lock failure. + assertCerts(store2, "refs/heads/master", addMaster); + assertCerts(store2, "refs/heads/branch"); + + assertEquals(FAST_FORWARD, store2.save()); + assertCerts(store2, "refs/heads/master", addMaster); + assertCerts(store2, "refs/heads/branch", addBranch); + } + + @Test + public void saveInBatch() throws Exception { + BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate(); + assertFalse(store.save(batch)); + assertEquals(0, batch.getCommands().size()); + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, newIdent()); + assertTrue(store.save(batch)); + + List<ReceiveCommand> commands = batch.getCommands(); + assertEquals(1, commands.size()); + ReceiveCommand cmd = commands.get(0); + assertEquals("refs/meta/push-certs", cmd.getRefName()); + assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult()); + + try (RevWalk rw = new RevWalk(repo)) { + batch.execute(rw, NullProgressMonitor.INSTANCE); + assertEquals(ReceiveCommand.Result.OK, cmd.getResult()); + } + } + + @Test + public void putMatchingWithNoMatchingRefs() throws Exception { + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master"), + command(zeroId(), ID2, "refs/heads/branch")); + store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList()); + assertEquals(NO_CHANGE, store.save()); + } + + @Test + public void putMatchingWithNoMatchingRefsInBatchOnEmptyRef() + throws Exception { + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master"), + command(zeroId(), ID2, "refs/heads/branch")); + store.put(addMaster, newIdent(), Collections.<ReceiveCommand> emptyList()); + BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate(); + assertFalse(store.save(batch)); + assertEquals(0, batch.getCommands().size()); + } + + @Test + public void putMatchingWithNoMatchingRefsInBatchOnNonEmptyRef() + throws Exception { + PushCertificate addMaster = newCert( + command(zeroId(), ID1, "refs/heads/master")); + store.put(addMaster, newIdent()); + assertEquals(NEW, store.save()); + + PushCertificate addBranch = newCert( + command(zeroId(), ID2, "refs/heads/branch")); + store.put(addBranch, newIdent(), Collections.<ReceiveCommand> emptyList()); + BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate(); + assertFalse(store.save(batch)); + assertEquals(0, batch.getCommands().size()); + } + + @Test + public void putMatchingWithSomeMatchingRefs() throws Exception { + PushCertificate addMasterAndBranch = newCert( + command(zeroId(), ID1, "refs/heads/master"), + command(zeroId(), ID2, "refs/heads/branch")); + store.put(addMasterAndBranch, newIdent(), + Collections.singleton(addMasterAndBranch.getCommands().get(0))); + assertEquals(NEW, store.save()); + assertCerts("refs/heads/master", addMasterAndBranch); + assertCerts("refs/heads/branch"); + } + + private PersonIdent newIdent() { + return new PersonIdent( + "A U. Thor", "author@example.com", ts.getAndIncrement(), 0); + } + + private PushCertificateStore newStore() { + return new PushCertificateStore(repo); + } + + private void assertCerts(String refName, PushCertificate... expected) + throws Exception { + assertCerts(store, refName, expected); + assertCerts(newStore(), refName, expected); + } + + private static void assertCerts(PushCertificateStore store, String refName, + PushCertificate... expected) throws Exception { + List<PushCertificate> ex = Arrays.asList(expected); + PushCertificate first = !ex.isEmpty() ? ex.get(0) : null; + assertEquals(first, store.get(refName)); + assertEquals(ex, toList(store.getAll(refName))); + } + + private static <T> List<T> toList(Iterable<T> it) { + List<T> list = new ArrayList<>(); + for (T t : it) { + list.add(t); + } + return list; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java index 8c7c992b70..745c322013 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java @@ -3,6 +3,7 @@ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2013, Robin Stocker <robin@nibor.org> + * Copyright (C) 2015, Patrick Steinhardt <ps@pks.im> * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -379,6 +380,56 @@ public class URIishTest { } @Test + public void testSshProtoHostOnly() throws Exception { + final String str = "ssh://example.com/"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/", u.getRawPath()); + assertEquals("/", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals("ssh://example.com/", u.toString()); + assertEquals("ssh://example.com/", u.toASCIIString()); + assertEquals("example.com", u.getHumanishName()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testSshProtoHostWithAuthentication() throws Exception { + final String str = "ssh://user:secret@pass@example.com/"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/", u.getRawPath()); + assertEquals("/", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals("ssh://user@example.com/", u.toString()); + assertEquals("ssh://user@example.com/", u.toASCIIString()); + assertEquals("example.com", u.getHumanishName()); + assertEquals("user", u.getUser()); + assertEquals("secret@pass", u.getPass()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testSshProtoHostWithPort() throws Exception { + final String str = "ssh://example.com:2222/"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/", u.getRawPath()); + assertEquals("/", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(2222, u.getPort()); + assertEquals("ssh://example.com:2222/", u.toString()); + assertEquals("ssh://example.com:2222/", u.toASCIIString()); + assertEquals("example.com", u.getHumanishName()); + assertEquals(u, new URIish(str)); + } + + @Test public void testSshProtoWithUserAndPort() throws Exception { final String str = "ssh://user@example.com:33/some/p ath"; URIish u = new URIish(str); @@ -623,6 +674,13 @@ public class URIishTest { } @Test + public void testGetEmptyHumanishNameWithAuthorityOnly() throws IllegalArgumentException, + URISyntaxException { + String humanishName = new URIish(GIT_SCHEME + "abc").getHumanishName(); + assertEquals("abc", humanishName); + } + + @Test public void testGetValidSlashHumanishName() throws IllegalArgumentException, URISyntaxException { String humanishName = new URIish(GIT_SCHEME + "host/abc/") diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java index 9dc5fac5d8..4625f30683 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java @@ -48,6 +48,8 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import org.junit.After; import org.junit.Before; @@ -83,4 +85,14 @@ public class FileUtils7Test { assertTrue(dir.exists()); assertTrue(file.exists()); } + + @Test + public void testAtomicMove() throws IOException { + File src = new File(trash, "src"); + Files.createFile(src.toPath()); + File dst = new File(trash, "dst"); + FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); + assertFalse(Files.exists(src.toPath())); + assertTrue(Files.exists(dst.toPath())); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java new file mode 100644 index 0000000000..928fb2ed9a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IOReadLineTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.util; + +import static org.junit.Assert.assertEquals; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; + +import org.eclipse.jgit.lib.Constants; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class IOReadLineTest { + @Parameter(0) + public boolean buffered; + + @Parameter(1) + public int sizeHint; + + @SuppressWarnings("boxing") + @Parameters(name="buffered={0}, sizeHint={1}") + public static Collection<Object[]> getParameters() { + Boolean[] bv = {false, true}; + Integer[] sv = {-1, 0, 1, 2, 3, 4, 64}; + Collection<Object[]> params = new ArrayList<>(bv.length * sv.length); + for (boolean b : bv) { + for (Integer s : sv) { + params.add(new Object[]{b, s}); + } + } + return params; + } + + @Test + public void testReadLine() throws Exception { + Reader r = newReader("foo\nbar\nbaz\n"); + assertEquals("foo\n", readLine(r)); + assertEquals("bar\n", readLine(r)); + assertEquals("baz\n", readLine(r)); + assertEquals("", readLine(r)); + } + + @Test + public void testReadLineNoTrailingNewline() throws Exception { + Reader r = newReader("foo\nbar\nbaz"); + assertEquals("foo\n", readLine(r)); + assertEquals("bar\n", readLine(r)); + assertEquals("baz", readLine(r)); + assertEquals("", readLine(r)); + } + + private String readLine(Reader r) throws Exception { + return IO.readLine(r, sizeHint); + } + + private Reader newReader(String in) { + Reader r = new InputStreamReader( + new ByteArrayInputStream(Constants.encode(in))); + if (buffered) { + r = new BufferedReader(r); + } + assertEquals(Boolean.valueOf(buffered), + Boolean.valueOf(r.markSupported())); + return r; + } +} diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF index 2cd9dc6ad0..3586cbcfc7 100644 --- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF @@ -3,14 +3,14 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.ui -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Vendor: %provider_name Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Export-Package: org.eclipse.jgit.awtui;version="4.0.3" -Import-Package: org.eclipse.jgit.errors;version="[4.0.3,4.1.0)", - org.eclipse.jgit.lib;version="[4.0.3,4.1.0)", - org.eclipse.jgit.nls;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revplot;version="[4.0.3,4.1.0)", - org.eclipse.jgit.revwalk;version="[4.0.3,4.1.0)", - org.eclipse.jgit.transport;version="[4.0.3,4.1.0)", - org.eclipse.jgit.util;version="[4.0.3,4.1.0)" +Export-Package: org.eclipse.jgit.awtui;version="4.1.2" +Import-Package: org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", + org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", + org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)", + org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", + org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", + org.eclipse.jgit.util;version="[4.1.2,4.2.0)" diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml index 3ffda41544..776cb8b101 100644 --- a/org.eclipse.jgit.ui/pom.xml +++ b/org.eclipse.jgit.ui/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit.ui</artifactId> diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java index 4a11964473..7359093a12 100644 --- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java +++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java @@ -82,6 +82,7 @@ class SwingCommitList extends PlotCommitList<SwingCommitList.SwingLane> { } static class SwingLane extends PlotLane { + private static final long serialVersionUID = 1L; Color color; } } diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index 124435a810..a1e79e2d29 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -1,9 +1,23 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <component id="org.eclipse.jgit" version="2"> - <resource path="src/org/eclipse/jgit/util/FileUtil.java" type="org.eclipse.jgit.util.FileUtil"> - <filter comment="moved into another bundle keeping original package" id="1110441988"> + <resource path="src/org/eclipse/jgit/transport/PushCertificate.java" type="org.eclipse.jgit.transport.PushCertificate"> + <filter comment="PushCertificate wasn't really usable in 4.0" id="338722907"> <message_arguments> - <message_argument value="org.eclipse.jgit.util.FileUtil"/> + <message_argument value="org.eclipse.jgit.transport.PushCertificate"/> + <message_argument value="PushCertificate()"/> + </message_arguments> + </filter> + <filter comment="PushCertificate wasn't really usable in 4.0" id="338792546"> + <message_arguments> + <message_argument value="org.eclipse.jgit.transport.PushCertificate"/> + <message_argument value="getCommandList()"/> + </message_arguments> + </filter> + </resource> + <resource path="src/org/eclipse/jgit/transport/PushCertificateParser.java" type="org.eclipse.jgit.transport.PushCertificateParser"> + <filter comment="PushCertificates haven't been really usable in 4.0" id="338849923"> + <message_arguments> + <message_argument value="org.eclipse.jgit.transport.PushCertificateParser"/> </message_arguments> </filter> </resource> diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs index ff39d16ab7..4e28e0b26b 100644 --- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs @@ -1,10 +1,10 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 @@ -67,11 +67,11 @@ org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullReference=error org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error -org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=error org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index edf38e409e..a51bee8406 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -2,10 +2,11 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit -Bundle-Version: 4.0.3.201509231615-r +Bundle-Version: 4.1.2.201602141800-r Bundle-Localization: plugin Bundle-Vendor: %provider_name -Export-Package: org.eclipse.jgit.api;version="4.0.3"; +Bundle-ActivationPolicy: lazy +Export-Package: org.eclipse.jgit.api;version="4.1.2"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, @@ -19,56 +20,60 @@ Export-Package: org.eclipse.jgit.api;version="4.0.3"; org.eclipse.jgit.submodule, org.eclipse.jgit.transport, org.eclipse.jgit.merge", - org.eclipse.jgit.api.errors;version="4.0.3"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors", - org.eclipse.jgit.attributes;version="4.0.3", - org.eclipse.jgit.blame;version="4.0.3"; + org.eclipse.jgit.api.errors;version="4.1.2"; + uses:="org.eclipse.jgit.lib, + org.eclipse.jgit.errors", + org.eclipse.jgit.attributes;version="4.1.2", + org.eclipse.jgit.blame;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff", - org.eclipse.jgit.diff;version="4.0.3"; + org.eclipse.jgit.diff;version="4.1.2"; uses:="org.eclipse.jgit.patch, org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util", - org.eclipse.jgit.dircache;version="4.0.3"; + org.eclipse.jgit.dircache;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.util, org.eclipse.jgit.events, org.eclipse.jgit.attributes", - org.eclipse.jgit.errors;version="4.0.3"; + org.eclipse.jgit.errors;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.internal.storage.pack, org.eclipse.jgit.transport, org.eclipse.jgit.dircache", - org.eclipse.jgit.events;version="4.0.3"; + org.eclipse.jgit.events;version="4.1.2"; uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.fnmatch;version="4.0.3", - org.eclipse.jgit.gitrepo;version="4.0.3"; + org.eclipse.jgit.fnmatch;version="4.1.2", + org.eclipse.jgit.gitrepo;version="4.1.2"; uses:="org.eclipse.jgit.api, org.eclipse.jgit.lib, - org.eclipse.jgit.revwalk", - org.eclipse.jgit.gitrepo.internal;version="4.0.3";x-internal:=true, - org.eclipse.jgit.hooks;version="4.0.3", - org.eclipse.jgit.ignore;version="4.0.3", - org.eclipse.jgit.ignore.internal;version="4.0.3";x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.internal;version="4.0.3";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", - org.eclipse.jgit.internal.storage.dfs;version="4.0.3"; + org.eclipse.jgit.revwalk, + org.xml.sax.helpers, + org.xml.sax", + org.eclipse.jgit.gitrepo.internal;version="4.1.2";x-internal:=true, + org.eclipse.jgit.hooks;version="4.1.2"; + uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.ignore;version="4.1.2", + org.eclipse.jgit.ignore.internal;version="4.1.2";x-friends:="org.eclipse.jgit.test", + org.eclipse.jgit.internal;version="4.1.2";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", + org.eclipse.jgit.internal.storage.dfs;version="4.1.2"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.http.server", - org.eclipse.jgit.internal.storage.file;version="4.0.3"; + org.eclipse.jgit.internal.storage.file;version="4.1.2"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.junit, org.eclipse.jgit.junit.http, org.eclipse.jgit.http.server, org.eclipse.jgit.java7.test, org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.storage.pack;version="4.0.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", - org.eclipse.jgit.lib;version="4.0.3"; + org.eclipse.jgit.internal.storage.pack;version="4.1.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.lib;version="4.1.2"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, @@ -78,41 +83,45 @@ Export-Package: org.eclipse.jgit.api;version="4.0.3"; org.eclipse.jgit.treewalk, org.eclipse.jgit.transport, org.eclipse.jgit.submodule", - org.eclipse.jgit.merge;version="4.0.3"; + org.eclipse.jgit.merge;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.diff, org.eclipse.jgit.dircache, org.eclipse.jgit.api", - org.eclipse.jgit.nls;version="4.0.3", - org.eclipse.jgit.notes;version="4.0.3"; + org.eclipse.jgit.nls;version="4.1.2", + org.eclipse.jgit.notes;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.merge", - org.eclipse.jgit.patch;version="4.0.3"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff", - org.eclipse.jgit.revplot;version="4.0.3"; + org.eclipse.jgit.patch;version="4.1.2"; + uses:="org.eclipse.jgit.lib, + org.eclipse.jgit.diff", + org.eclipse.jgit.revplot;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk", - org.eclipse.jgit.revwalk;version="4.0.3"; + org.eclipse.jgit.revwalk;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, org.eclipse.jgit.revwalk.filter", - org.eclipse.jgit.revwalk.filter;version="4.0.3"; - uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.util", - org.eclipse.jgit.storage.file;version="4.0.3"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util", - org.eclipse.jgit.storage.pack;version="4.0.3"; + org.eclipse.jgit.revwalk.filter;version="4.1.2"; + uses:="org.eclipse.jgit.revwalk, + org.eclipse.jgit.lib, + org.eclipse.jgit.util", + org.eclipse.jgit.storage.file;version="4.1.2"; + uses:="org.eclipse.jgit.lib, + org.eclipse.jgit.util", + org.eclipse.jgit.storage.pack;version="4.1.2"; uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.submodule;version="4.0.3"; + org.eclipse.jgit.submodule;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.treewalk", - org.eclipse.jgit.transport;version="4.0.3"; + org.eclipse.jgit.transport;version="4.1.2"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.revwalk, org.eclipse.jgit.internal.storage.pack, @@ -124,29 +133,29 @@ Export-Package: org.eclipse.jgit.api;version="4.0.3"; org.eclipse.jgit.transport.http, org.eclipse.jgit.errors, org.eclipse.jgit.storage.pack", - org.eclipse.jgit.transport.http;version="4.0.3"; + org.eclipse.jgit.transport.http;version="4.1.2"; uses:="javax.net.ssl", - org.eclipse.jgit.transport.resolver;version="4.0.3"; + org.eclipse.jgit.transport.resolver;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.transport", - org.eclipse.jgit.treewalk;version="4.0.3"; + org.eclipse.jgit.treewalk;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.attributes, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, org.eclipse.jgit.dircache", - org.eclipse.jgit.treewalk.filter;version="4.0.3"; + org.eclipse.jgit.treewalk.filter;version="4.1.2"; uses:="org.eclipse.jgit.treewalk", - org.eclipse.jgit.util;version="4.0.3"; + org.eclipse.jgit.util;version="4.1.2"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.transport.http, org.eclipse.jgit.storage.file, org.ietf.jgss", - org.eclipse.jgit.util.io;version="4.0.3" -Bundle-ActivationPolicy: lazy + org.eclipse.jgit.util.io;version="4.1.2" Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: com.jcraft.jsch;bundle-version="[0.1.37,0.2.0)" +Require-Bundle: com.jcraft.jsch;bundle-version="[0.1.37,0.2.0)", + org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", javax.crypto, javax.net.ssl, diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF index b7c2a35af8..c7d9753bf5 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: 4.0.3.201509231615-r -Eclipse-SourceBundle: org.eclipse.jgit;version="4.0.3.201509231615-r";roots="." +Bundle-Version: 4.1.2.201602141800-r +Eclipse-SourceBundle: org.eclipse.jgit;version="4.1.2.201602141800-r";roots="." diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml index 3dc71322f0..945b135139 100644 --- a/org.eclipse.jgit/pom.xml +++ b/org.eclipse.jgit/pom.xml @@ -53,7 +53,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> </parent> <artifactId>org.eclipse.jgit</artifactId> @@ -88,6 +88,12 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> + + <dependency> + <groupId>org.eclipse.jdt</groupId> + <artifactId>org.eclipse.jdt.annotation</artifactId> + <version>1.1.0</version> + </dependency> </dependencies> <build> diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index db9a684585..34bbb415ba 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -59,23 +59,23 @@ cannotCreateIndexfile=Cannot create an index file with name {0} cannotCreateTempDir=Cannot create a temp dir cannotDeleteCheckedOutBranch=Branch {0} is checked out and can not be deleted cannotDeleteFile=Cannot delete file: {0} -cannotDeleteObjectsPath="Can't delete {0}/{1}: {2} +cannotDeleteObjectsPath=Cannot delete {0}/{1}: {2} cannotDeleteStaleTrackingRef=Cannot delete stale tracking ref {0} cannotDeleteStaleTrackingRef2=Cannot delete stale tracking ref {0}: {1} cannotDetermineProxyFor=Cannot determine proxy for {0} cannotDownload=Cannot download {0} -cannotEnterObjectsPath=Can't enter {0}/objects: {1} -cannotEnterPathFromParent=Can't enter {0} from {1}: {2} +cannotEnterObjectsPath=Cannot enter {0}/objects: {1} +cannotEnterPathFromParent=Cannot enter {0} from {1}: {2} cannotExecute=cannot execute: {0} cannotGet=Cannot get {0} -cannotGetObjectsPath=Can't get {0}/{1}: {2} -cannotListObjectsPath=Can't ls {0}/{1}: {2} -cannotListPackPath=Can't ls {0}/pack: {1} +cannotGetObjectsPath=Cannot get {0}/{1}: {2} +cannotListObjectsPath=Cannot ls {0}/{1}: {2} +cannotListPackPath=Cannot ls {0}/pack: {1} cannotListRefs=cannot list refs cannotLock=Cannot lock {0} cannotLockPackIn=Cannot lock pack in {0} cannotMatchOnEmptyString=Cannot match on empty string. -cannotMkdirObjectPath=Can't mkdir {0}/{1}: {2} +cannotMkdirObjectPath=Cannot mkdir {0}/{1}: {2} cannotMoveIndexTo=Cannot move index to {0} cannotMovePackTo=Cannot move pack to {0} cannotOpenService=cannot open {0} @@ -97,7 +97,7 @@ cannotStoreObjects=cannot store objects cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID cannotUnloadAModifiedTree=Cannot unload a modified tree. cannotWorkWithOtherStagesThanZeroRightNow=Cannot work with other stages than zero right now. Won't write corrupt index. -cannotWriteObjectsPath="Can't write {0}/{1}: {2} +cannotWriteObjectsPath=Cannot write {0}/{1}: {2} canOnlyCherryPickCommitsWithOneParent=Cannot cherry-pick commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported. canOnlyRevertCommitsWithOneParent=Cannot revert commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported commitDoesNotHaveGivenParent=The commit ''{0}'' does not have a parent number {1}. @@ -237,12 +237,10 @@ errorDecodingFromFile=Error decoding from file {0} errorEncodingFromFile=Error encoding from file {0} errorInBase64CodeReadingStream=Error in Base64 code reading stream. errorInPackedRefs=error in packed-refs -errorInvalidPushCert=error: invalid protocol: {0} errorInvalidProtocolWantedOldNewRef=error: invalid protocol: wanted 'old new ref' errorListing=Error listing {0} errorOccurredDuringUnpackingOnTheRemoteEnd=error occurred during unpacking on the remote end: {0} errorReadingInfoRefs=error reading info/refs -errorSymlinksNotSupported=Symlinks are not supported with this OS/JRE exceptionCaughtDuringExecutionOfHook=Exception caught during execution of "{0}" hook. exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command @@ -288,7 +286,6 @@ funnyRefname=funny refname gcFailed=Garbage collection failed. gitmodulesNotFound=.gitmodules not found in tree. headRequiredToStash=HEAD required to stash local changes -hiddenFilesStartWithDot=Hiding only allowed for names that start with a period hoursAgo={0} hours ago hugeIndexesAreNotSupportedByJgitYet=Huge indexes are not supported by jgit, yet hunkBelongsToAnotherFile=Hunk belongs to another file @@ -326,8 +323,10 @@ invalidEncryption=Invalid encryption invalidGitdirRef = Invalid .git reference in file ''{0}'' invalidGitType=invalid git type: {0} invalidId=Invalid id: {0} +invalidId0=Invalid id invalidIdLength=Invalid id length {0}; should be {1} invalidIgnoreParamSubmodule=Found invalid ignore param for submodule {0}. +invalidIgnoreRule=Exception caught while parsing ignore rule ''{0}''. invalidIntegerValue=Invalid integer value: {0}.{1}={2} invalidKey=Invalid key: {0} invalidLineInConfigFile=Invalid line in config file @@ -344,6 +343,7 @@ invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} invalidReflogRevision=Invalid reflog revision: {0} invalidRefName=Invalid ref name: {0} invalidRemote=Invalid remote: {0} +invalidShallowObject=invalid shallow object {0}, expected commit invalidStageForPath=Invalid stage {0} for path {1} invalidTagOption=Invalid tag option: {0} invalidTimeout=Invalid timeout: {0} @@ -440,6 +440,7 @@ outputHasAlreadyBeenStarted=Output has already been started. packChecksumMismatch=Pack checksum mismatch detected for pack file {0} packCorruptedWhileWritingToFilesystem=Pack corrupted while writing to filesystem packDoesNotMatchIndex=Pack {0} does not match index +packedRefsHandleIsStale=packed-refs handle is stale, {0}. retry packetSizeMustBeAtLeast=packet size {0} must be >= {1} packetSizeMustBeAtMost=packet size {0} must be <= {1} packfileCorruptionDetected=Packfile corruption detected: {0} @@ -464,7 +465,7 @@ peeledLineBeforeRef=Peeled line before ref. peerDidNotSupplyACompleteObjectGraph=peer did not supply a complete object graph personIdentEmailNonNull=E-mail address of PersonIdent must not be null. personIdentNameNonNull=Name of PersonIdent must not be null. -prefixRemote=remote: +prefixRemote=remote: problemWithResolvingPushRefSpecsLocally=Problem with resolving push ref specs locally: {0} progressMonUploading=Uploading {0} propertyIsAlreadyNonNull=Property is already non null @@ -473,6 +474,10 @@ pruneLooseUnreferencedObjects=Prune loose, unreferenced objects pullOnRepoWithoutHEADCurrentlyNotSupported=Pull on repository without HEAD currently not supported pullTaskName=Pull pushCancelled=push cancelled +pushCertificateInvalidField=Push certificate has missing or invalid value for {0} +pushCertificateInvalidFieldValue=Push certificate has missing or invalid value for {0}: {1} +pushCertificateInvalidHeader=Push certificate has invalid header format +pushCertificateInvalidSignature=Push certificate has invalid signature format pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport pushNotPermitted=push not permitted rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry @@ -531,7 +536,6 @@ selectingCommits=Selecting commits sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm. serviceNotEnabledNoName=Service not enabled serviceNotPermitted={0} not permitted -serviceNotPermittedNoName=Service not permitted shallowCommitsAlreadyInitialized=Shallow commits have already been initialized shortCompressedStreamAt=Short compressed stream at {0} shortReadOfBlock=Short read of block. @@ -561,6 +565,9 @@ stashDropMissingReflog=Stash reflog does not contain entry ''{0}'' stashFailed=Stashing local changes did not successfully complete stashResolveFailed=Reference ''{0}'' does not resolve to stashed commit statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled +storePushCertMultipleRefs=Store push certificate for {0} refs +storePushCertOneRef=Store push certificate for {0} +storePushCertReflog=Store push certificate submoduleExists=Submodule ''{0}'' already exists in the index submoduleParentRemoteUrlInvalid=Cannot remove segment from remote url ''{0}'' submodulesNotSupported=Submodules are not supported @@ -602,6 +609,7 @@ unableToCheckConnectivity=Unable to check connectivity. unableToCreateNewObject=Unable to create new object: {0} unableToStore=Unable to store {0}. unableToWrite=Unable to write {0} +unauthorized=Unauthorized unencodeableFile=Unencodable file: {0} unexpectedCompareResult=Unexpected metadata comparison result: {0} unexpectedEndOfConfigFile=Unexpected end of config file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index 53901f589c..b3bc319aef 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -127,16 +127,23 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { */ public Git call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { + Repository repository = null; try { URIish u = new URIish(uri); - Repository repository = init(u); + repository = init(u); FetchResult result = fetch(repository, u); if (!noCheckout) checkout(repository, result); - return new Git(repository); + return new Git(repository, true); } catch (IOException ioe) { + if (repository != null) { + repository.close(); + } throw new JGitInternalException(ioe.getMessage(), ioe); } catch (URISyntaxException e) { + if (repository != null) { + repository.close(); + } throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote)); } @@ -331,7 +338,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { /** * @param uri - * the uri to clone from + * the URI to clone from, or {@code null} to unset the URI. + * The URI must be set before {@link #call} is called. * @return this instance */ public CloneCommand setURI(String uri) { @@ -346,7 +354,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { * @see URIish#getHumanishName() * * @param directory - * the directory to clone to + * the directory to clone to, or {@code null} if the directory + * name should be taken from the source uri * @return this instance * @throws IllegalStateException * if the combination of directory, gitDir and bare is illegal. @@ -362,7 +371,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { /** * @param gitDir - * the repository meta directory + * the repository meta directory, or {@code null} to choose one + * automatically at clone time * @return this instance * @throws IllegalStateException * if the combination of directory, gitDir and bare is illegal. @@ -400,10 +410,14 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { * * @see Constants#DEFAULT_REMOTE_NAME * @param remote - * name that keeps track of the upstream repository + * name that keeps track of the upstream repository. + * {@code null} means to use DEFAULT_REMOTE_NAME. * @return this instance */ public CloneCommand setRemote(String remote) { + if (remote == null) { + remote = Constants.DEFAULT_REMOTE_NAME; + } this.remote = remote; return this; } @@ -413,9 +427,15 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { * the initial branch to check out when cloning the repository. * Can be specified as ref name (<code>refs/heads/master</code>), * branch name (<code>master</code>) or tag name (<code>v1.2.3</code>). + * The default is to use the branch pointed to by the cloned + * repository's HEAD and can be requested by passing {@code null} + * or <code>HEAD</code>. * @return this instance */ public CloneCommand setBranch(String branch) { + if (branch == null) { + branch = Constants.HEAD; + } this.branch = branch; return this; } @@ -430,6 +450,9 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { * @return {@code this} */ public CloneCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } this.monitor = monitor; return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java index 527daef811..3e3a7a89c8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java @@ -268,7 +268,10 @@ public class DiffCommand extends GitCommand<List<DiffEntry>> { * @return this instance */ public DiffCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } this.monitor = monitor; return this; } -}
\ No newline at end of file +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java index 29d475a221..9620089b08 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java @@ -244,6 +244,9 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { */ public FetchCommand setProgressMonitor(ProgressMonitor monitor) { checkCallable(); + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } this.monitor = monitor; return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 1e9fe5c25f..addca4c469 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -207,7 +207,7 @@ public class Git implements AutoCloseable { this(repo, false); } - private Git(Repository repo, boolean closeRepo) { + Git(Repository repo, boolean closeRepo) { if (repo == null) throw new NullPointerException(); this.repo = repo; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java index 63de85c502..2783edd5d8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java @@ -130,6 +130,9 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { * @return this instance */ public PullCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } this.monitor = monitor; return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java index 0e1ce58313..227e32236d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java @@ -257,6 +257,9 @@ public class PushCommand extends */ public PushCommand setProgressMonitor(ProgressMonitor monitor) { checkCallable(); + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } this.monitor = monitor; return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java index 7196a2f386..ff29008420 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -1493,6 +1493,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> { * @return this instance */ public RebaseCommand setProgressMonitor(ProgressMonitor monitor) { + if (monitor == null) { + monitor = NullProgressMonitor.INSTANCE; + } this.monitor = monitor; return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java index 06c8f414e3..fbb24c1577 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java @@ -173,6 +173,8 @@ public class SubmoduleAddCommand extends CloneCommand clone = Git.cloneRepository(); configure(clone); clone.setDirectory(moduleDirectory); + clone.setGitDir(new File(new File(repo.getDirectory(), + Constants.MODULES), path)); clone.setURI(resolvedUri); if (monitor != null) clone.setProgressMonitor(monitor); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java index c8d96a0263..b9f2a5617d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java @@ -45,6 +45,15 @@ public class RefNotFoundException extends GitAPIException { /** * @param message + * @param cause + * @since 4.1 + */ + public RefNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message */ public RefNotFoundException(String message) { super(message); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java index 25d7e4d474..1d54f77db8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/StashApplyFailureException.java @@ -10,6 +10,15 @@ public class StashApplyFailureException extends GitAPIException { private static final long serialVersionUID = 1L; /** + * @param message + * @param cause + * @since 4.1 + */ + public StashApplyFailureException(String message, Throwable cause) { + super(message, cause); + } + + /** * Create a StashApplyFailedException * * @param message diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java index 0990040150..082f94c65d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java @@ -61,4 +61,13 @@ public class UnmergedPathsException extends GitAPIException { public UnmergedPathsException(Throwable cause) { super(JGitText.get().unmergedPaths, cause); } + + /** + * @param message + * @param cause + * @since 4.1 + */ + public UnmergedPathsException(String message, Throwable cause) { + super(message, cause); + } } 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 bcc30c35b4..fc701f3a54 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -665,16 +665,12 @@ public class DiffFormatter implements AutoCloseable { format(res.header, res.a, res.b); } - private static void writeGitLinkDiffText(OutputStream o, DiffEntry ent) - throws IOException { - if (ent.getOldMode() == GITLINK) { - o.write(encodeASCII("-Subproject commit " + ent.getOldId().name() //$NON-NLS-1$ - + "\n")); //$NON-NLS-1$ - } - if (ent.getNewMode() == GITLINK) { - o.write(encodeASCII("+Subproject commit " + ent.getNewId().name() //$NON-NLS-1$ - + "\n")); //$NON-NLS-1$ + private static byte[] writeGitLinkText(AbbreviatedObjectId id) { + if (id.toObjectId().equals(ObjectId.zeroId())) { + return EMPTY; } + return encodeASCII("Subproject commit " + id.name() //$NON-NLS-1$ + + "\n"); //$NON-NLS-1$ } private String format(AbbreviatedObjectId id) { @@ -938,13 +934,7 @@ public class DiffFormatter implements AutoCloseable { formatHeader(buf, ent); - if (ent.getOldMode() == GITLINK || ent.getNewMode() == GITLINK) { - formatOldNewPaths(buf, ent); - writeGitLinkDiffText(buf, ent); - editList = new EditList(); - type = PatchType.UNIFIED; - - } else if (ent.getOldId() == null || ent.getNewId() == null) { + if (ent.getOldId() == null || ent.getNewId() == null) { // Content not changed (e.g. only mode, pure rename) editList = new EditList(); type = PatchType.UNIFIED; @@ -952,8 +942,15 @@ public class DiffFormatter implements AutoCloseable { } else { assertHaveRepository(); - byte[] aRaw = open(OLD, ent); - byte[] bRaw = open(NEW, ent); + byte[] aRaw, bRaw; + + if (ent.getOldMode() == GITLINK || ent.getNewMode() == GITLINK) { + aRaw = writeGitLinkText(ent.getOldId()); + bRaw = writeGitLinkText(ent.getNewId()); + } else { + aRaw = open(OLD, ent); + bRaw = open(NEW, ent); + } if (aRaw == BINARY || bRaw == BINARY // || RawText.isBinary(aRaw) || RawText.isBinary(bRaw)) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java index 83aa8fa4de..f139afc00b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java @@ -478,6 +478,7 @@ public class DirCacheTree { // The entry is contained in this subtree. // + assert(st != null); st.validate(cache, cCnt, cIdx, pathOff + st.nameLength() + 1); cIdx += st.entrySpan; entrySpan += st.entrySpan; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java index e6ae685c00..5f9ce351ad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DiffInterruptedException.java @@ -51,4 +51,26 @@ package org.eclipse.jgit.errors; */ public class DiffInterruptedException extends RuntimeException { private static final long serialVersionUID = 1L; + + /** + * @param message + * @param cause + * @since 4.1 + */ + public DiffInterruptedException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message + * @since 4.1 + */ + public DiffInterruptedException(String message) { + super(message); + } + + /** Indicates that the thread computing a diff was interrupted. */ + public DiffInterruptedException() { + super(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java index b545312ae7..390545ffaf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java @@ -45,7 +45,8 @@ package org.eclipse.jgit.errors; -import java.io.UnsupportedEncodingException; +import static java.nio.charset.StandardCharsets.US_ASCII; + import java.text.MessageFormat; import org.eclipse.jgit.internal.JGitText; @@ -64,16 +65,25 @@ public class InvalidObjectIdException extends IllegalArgumentException { * @param length of the sequence of invalid bytes. */ public InvalidObjectIdException(byte[] bytes, int offset, int length) { - super(MessageFormat.format(JGitText.get().invalidId, asAscii(bytes, offset, length))); + super(msg(bytes, offset, length)); + } + + /** + * @param id the invalid id. + * + * @since 4.1 + */ + public InvalidObjectIdException(String id) { + super(MessageFormat.format(JGitText.get().invalidId, id)); } - private static String asAscii(byte[] bytes, int offset, int length) { + private static String msg(byte[] bytes, int offset, int length) { try { - return ": " + new String(bytes, offset, length, "US-ASCII"); //$NON-NLS-1$ //$NON-NLS-2$ - } catch (UnsupportedEncodingException e2) { - return ""; //$NON-NLS-1$ - } catch (StringIndexOutOfBoundsException e2) { - return ""; //$NON-NLS-1$ + return MessageFormat.format( + JGitText.get().invalidId, + new String(bytes, offset, length, US_ASCII)); + } catch (StringIndexOutOfBoundsException e) { + return JGitText.get().invalidId0; } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java index 18aa9d978a..0142e17635 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LockFailedException.java @@ -57,6 +57,20 @@ public class LockFailedException extends IOException { private File file; /** + * @param file + * file that could not be locked + * @param message + * exception message + * @param cause + * cause, for later retrieval by {@link Throwable#getCause()} + * @since 4.1 + */ + public LockFailedException(File file, String message, Throwable cause) { + super(message, cause); + this.file = file; + } + + /** * Construct a CannotLockException for the given file and message * * @param file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java index 5503bd19e2..44bc16492d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java @@ -60,7 +60,7 @@ public class PackProtocolException extends TransportException { * @param uri * URI used for transport * @param s - * message + * message, which may be shown to an end-user. */ public PackProtocolException(final URIish uri, final String s) { super(uri + ": " + s); //$NON-NLS-1$ @@ -73,7 +73,7 @@ public class PackProtocolException extends TransportException { * @param uri * URI used for transport * @param s - * message + * message, which may be shown to an end-user. * @param cause * root cause exception */ @@ -86,7 +86,7 @@ public class PackProtocolException extends TransportException { * Constructs an PackProtocolException with the specified detail message. * * @param s - * message + * message, which may be shown to an end-user. */ public PackProtocolException(final String s) { super(s); @@ -96,7 +96,7 @@ public class PackProtocolException extends TransportException { * Constructs an PackProtocolException with the specified detail message. * * @param s - * message + * message, which may be shown to an end-user. * @param cause * root cause exception */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java index fa27948a64..891479d1f4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java @@ -183,6 +183,9 @@ public class ManifestParser extends DefaultHandler { String qName, Attributes attributes) throws SAXException { if ("project".equals(qName)) { //$NON-NLS-1$ + if (attributes.getValue("name") == null) { //$NON-NLS-1$ + throw new SAXException(RepoText.get().invalidManifest); + } currentProject = new RepoProject( attributes.getValue("name"), //$NON-NLS-1$ attributes.getValue("path"), //$NON-NLS-1$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java index b39dd8a1f2..790f4db672 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -105,6 +105,7 @@ public class RepoCommand extends GitCommand<RevCommit> { private String uri; private String groups; private String branch; + private String targetBranch = Constants.HEAD; private PersonIdent author; private RemoteReader callback; private InputStream inputStream; @@ -224,27 +225,27 @@ public class RepoCommand extends GitCommand<RevCommit> { /** * @param repo */ - public RepoCommand(final Repository repo) { + public RepoCommand(Repository repo) { super(repo); } /** * Set path to the manifest XML file. - * + * <p> * Calling {@link #setInputStream} will ignore the path set here. * * @param path * (with <code>/</code> as separator) * @return this command */ - public RepoCommand setPath(final String path) { + public RepoCommand setPath(String path) { this.path = path; return this; } /** * Set the input stream to the manifest XML. - * + * <p> * Setting inputStream will ignore the path set. It will be closed in * {@link #call}. * @@ -252,7 +253,7 @@ public class RepoCommand extends GitCommand<RevCommit> { * @return this command * @since 3.5 */ - public RepoCommand setInputStream(final InputStream inputStream) { + public RepoCommand setInputStream(InputStream inputStream) { this.inputStream = inputStream; return this; } @@ -263,7 +264,7 @@ public class RepoCommand extends GitCommand<RevCommit> { * @param uri * @return this command */ - public RepoCommand setURI(final String uri) { + public RepoCommand setURI(String uri) { this.uri = uri; return this; } @@ -274,14 +275,14 @@ public class RepoCommand extends GitCommand<RevCommit> { * @param groups groups separated by comma, examples: default|all|G1,-G2,-G3 * @return this command */ - public RepoCommand setGroups(final String groups) { + public RepoCommand setGroups(String groups) { this.groups = groups; return this; } /** * Set default branch. - * + * <p> * This is generally the name of the branch the manifest file was in. If * there's no default revision (branch) specified in manifest and no * revision specified in project, this branch will be used. @@ -289,12 +290,30 @@ public class RepoCommand extends GitCommand<RevCommit> { * @param branch * @return this command */ - public RepoCommand setBranch(final String branch) { + public RepoCommand setBranch(String branch) { this.branch = branch; return this; } /** + * Set target branch. + * <p> + * This is the target branch of the super project to be updated. If not set, + * default is HEAD. + * <p> + * For non-bare repositories, HEAD will always be used and this will be + * ignored. + * + * @param branch + * @return this command + * @since 4.1 + */ + public RepoCommand setTargetBranch(String branch) { + this.targetBranch = Constants.R_HEADS + branch; + return this; + } + + /** * The progress monitor associated with the clone operation. By default, * this is set to <code>NullProgressMonitor</code> * @@ -309,7 +328,7 @@ public class RepoCommand extends GitCommand<RevCommit> { /** * Set the author/committer for the bare repository commit. - * + * <p> * For non-bare repositories, the current user will be used and this will be * ignored. * @@ -445,7 +464,7 @@ public class RepoCommand extends GitCommand<RevCommit> { ObjectId treeId = index.writeTree(inserter); // Create a Commit object, populate it and write it - ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$ + ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$ CommitBuilder commit = new CommitBuilder(); commit.setTreeId(treeId); if (headId != null) @@ -457,7 +476,7 @@ public class RepoCommand extends GitCommand<RevCommit> { ObjectId commitId = inserter.insert(commit); inserter.flush(); - RefUpdate ru = repo.updateRef(Constants.HEAD); + RefUpdate ru = repo.updateRef(targetBranch); ru.setNewObjectId(commitId); ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId()); Result rc = ru.update(rw); @@ -471,12 +490,14 @@ public class RepoCommand extends GitCommand<RevCommit> { case REJECTED: case LOCK_FAILURE: throw new ConcurrentRefUpdateException( - JGitText.get().couldNotLockHEAD, ru.getRef(), + MessageFormat.format( + JGitText.get().cannotLock, targetBranch), + ru.getRef(), rc); default: throw new JGitInternalException(MessageFormat.format( JGitText.get().updatingRefFailed, - Constants.HEAD, commitId.name(), rc)); + targetBranch, commitId.name(), rc)); } return rw.parseCommit(commitId); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java index 1fff1c3532..9a072114a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java @@ -138,6 +138,9 @@ public class RepoProject implements Comparable<RepoProject> { */ public RepoProject(String name, String path, String revision, String remote, String groups) { + if (name == null) { + throw new NullPointerException(); + } this.name = name; if (path != null) this.path = path; @@ -221,7 +224,7 @@ public class RepoProject implements Comparable<RepoProject> { /** * Get the name of the remote definition of the sub repo. * - * @return {@remote} + * @return {@code remote} */ public String getRemote() { return remote; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java index 2303ffd6d6..e376cbba5d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java @@ -47,6 +47,8 @@ import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH; import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.ignore.internal.IMatcher; import org.eclipse.jgit.ignore.internal.PathMatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * "Fast" (compared with IgnoreRule) git ignore rule implementation supporting @@ -57,6 +59,8 @@ import org.eclipse.jgit.ignore.internal.PathMatcher; * @since 3.6 */ public class FastIgnoreRule { + private final static Logger LOG = LoggerFactory + .getLogger(FastIgnoreRule.class); /** * Character used as default path separator for ignore entries @@ -98,24 +102,32 @@ public class FastIgnoreRule { if (pattern.charAt(0) == '#') { this.matcher = NO_MATCH; dirOnly = false; - } else { - dirOnly = pattern.charAt(pattern.length() - 1) == PATH_SEPARATOR; - if (dirOnly) { - pattern = stripTrailing(pattern, PATH_SEPARATOR); - if (pattern.length() == 0) { - this.matcher = NO_MATCH; - return; - } + return; + } + if (pattern.charAt(0) == '\\' && pattern.length() > 1) { + char next = pattern.charAt(1); + if (next == '!' || next == '#') { + // remove backslash escaping first special characters + pattern = pattern.substring(1); } - IMatcher m; - try { - m = PathMatcher.createPathMatcher(pattern, - Character.valueOf(PATH_SEPARATOR), dirOnly); - } catch (InvalidPatternException e) { - m = NO_MATCH; + } + dirOnly = pattern.charAt(pattern.length() - 1) == PATH_SEPARATOR; + if (dirOnly) { + pattern = stripTrailing(pattern, PATH_SEPARATOR); + if (pattern.length() == 0) { + this.matcher = NO_MATCH; + return; } - this.matcher = m; } + IMatcher m; + try { + m = PathMatcher.createPathMatcher(pattern, + Character.valueOf(PATH_SEPARATOR), dirOnly); + } catch (InvalidPatternException e) { + m = NO_MATCH; + LOG.error(e.getMessage(), e); + } + this.matcher = m; } /** @@ -176,6 +188,14 @@ public class FastIgnoreRule { return !inverse; } + /** + * @return true if the rule never matches (comment line or broken pattern) + * @since 4.1 + */ + public boolean isEmpty() { + return matcher == NO_MATCH; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java index efaeacd533..8b1244ed1b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java @@ -109,9 +109,12 @@ public class IgnoreNode { BufferedReader br = asReader(in); String txt; while ((txt = br.readLine()) != null) { - txt = txt.trim(); - if (txt.length() > 0 && !txt.startsWith("#") && !txt.equals("/")) //$NON-NLS-1$ //$NON-NLS-2$ - rules.add(new FastIgnoreRule(txt)); + if (txt.length() > 0 && !txt.startsWith("#") && !txt.equals("/")) { //$NON-NLS-1$ //$NON-NLS-2$ + FastIgnoreRule rule = new FastIgnoreRule(txt); + if (!rule.isEmpty()) { + rules.add(rule); + } + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java index f1153d9c69..3d0ad09124 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java @@ -50,7 +50,7 @@ package org.eclipse.jgit.ignore.internal; public class LeadingAsteriskMatcher extends NameMatcher { LeadingAsteriskMatcher(String pattern, Character pathSeparator, boolean dirOnly) { - super(pattern, pathSeparator, dirOnly); + super(pattern, pathSeparator, dirOnly, true); if (subPattern.charAt(0) != '*') throw new IllegalArgumentException( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java index 6c4c8092fe..8beae8379e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java @@ -58,9 +58,13 @@ public class NameMatcher extends AbstractMatcher { final String subPattern; - NameMatcher(String pattern, Character pathSeparator, boolean dirOnly) { + NameMatcher(String pattern, Character pathSeparator, boolean dirOnly, + boolean deleteBackslash) { super(pattern, dirOnly); slash = getPathSeparator(pathSeparator); + if (deleteBackslash) { + pattern = Strings.deleteBackslash(pattern); + } beginning = pattern.length() == 0 ? false : pattern.charAt(0) == slash; if (!beginning) this.subPattern = pattern; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java index d3e5f6a053..c3f6694a7a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java @@ -85,7 +85,8 @@ public class PathMatcher extends AbstractMatcher { } private boolean isSimplePathWithSegments(String path) { - return !isWildCard(path) && count(path, slash, true) > 0; + return !isWildCard(path) && path.indexOf('\\') < 0 + && count(path, slash, true) > 0; } static private List<IMatcher> createMatchers(List<String> segments, @@ -118,7 +119,7 @@ public class PathMatcher extends AbstractMatcher { public static IMatcher createPathMatcher(String pattern, Character pathSeparator, boolean dirOnly) throws InvalidPatternException { - pattern = pattern.trim(); + pattern = trim(pattern); char slash = Strings.getPathSeparator(pathSeparator); // ignore possible leading and trailing slash int slashIdx = pattern.indexOf(slash, 1); @@ -127,6 +128,29 @@ public class PathMatcher extends AbstractMatcher { return createNameMatcher0(pattern, pathSeparator, dirOnly); } + /** + * Trim trailing spaces, unless they are escaped with backslash, see + * https://www.kernel.org/pub/software/scm/git/docs/gitignore.html + * + * @param pattern + * non null + * @return trimmed pattern + */ + private static String trim(String pattern) { + while (pattern.length() > 0 + && pattern.charAt(pattern.length() - 1) == ' ') { + if (pattern.length() > 1 + && pattern.charAt(pattern.length() - 2) == '\\') { + // last space was escaped by backslash: remove backslash and + // keep space + pattern = pattern.substring(0, pattern.length() - 2) + " "; //$NON-NLS-1$ + return pattern; + } + pattern = pattern.substring(0, pattern.length() - 1); + } + return pattern; + } + private static IMatcher createNameMatcher0(String segment, Character pathSeparator, boolean dirOnly) throws InvalidPatternException { @@ -144,7 +168,7 @@ public class PathMatcher extends AbstractMatcher { case COMPLEX: return new WildCardMatcher(segment, pathSeparator, dirOnly); default: - return new NameMatcher(segment, pathSeparator, dirOnly); + return new NameMatcher(segment, pathSeparator, dirOnly, true); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java index cd4d7536d9..f972828bc6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java @@ -44,13 +44,16 @@ package org.eclipse.jgit.ignore.internal; import static java.lang.Character.isLetter; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.ignore.FastIgnoreRule; +import org.eclipse.jgit.internal.JGitText; /** * Various {@link String} related utility methods, written mostly to avoid @@ -138,16 +141,36 @@ public class Strings { private static boolean isComplexWildcard(String pattern) { int idx1 = pattern.indexOf('['); if (idx1 != -1) { - int idx2 = pattern.indexOf(']'); + int idx2 = pattern.indexOf(']', idx1); if (idx2 > idx1) return true; } - // required to match escaped backslashes '\\\\' - if (pattern.indexOf('?') != -1 || pattern.indexOf('\\') != -1) + if (pattern.indexOf('?') != -1) { return true; + } else { + // check if the backslash escapes one of the glob special characters + // if not, backslash is not part of a regex and treated literally + int backSlash = pattern.indexOf('\\'); + if (backSlash >= 0) { + int nextIdx = backSlash + 1; + if (pattern.length() == nextIdx) { + return false; + } + char nextChar = pattern.charAt(nextIdx); + if (escapedByBackslash(nextChar)) { + return true; + } else { + return false; + } + } + } return false; } + private static boolean escapedByBackslash(char nextChar) { + return nextChar == '?' || nextChar == '*' || nextChar == '['; + } + static PatternState checkWildCards(String pattern) { if (isComplexWildcard(pattern)) return PatternState.COMPLEX; @@ -226,7 +249,7 @@ public class Strings { char[] charClass = new char[6]; for (int i = 0; i < pattern.length(); i++) { - char c = pattern.charAt(i); + final char c = pattern.charAt(i); switch (c) { case '*': @@ -236,6 +259,20 @@ public class Strings { sb.append('.').append(c); break; + case '(': // fall-through + case ')': // fall-through + case '{': // fall-through + case '}': // fall-through + case '+': // fall-through + case '$': // fall-through + case '^': // fall-through + case '|': + if (seenEscape || in_brackets > 0) + sb.append(c); + else + sb.append('\\').append(c); + break; + case '.': if (seenEscape) sb.append(c); @@ -273,6 +310,14 @@ public class Strings { char lookAhead = lookAhead(pattern, i); if (lookAhead == ']' || lookAhead == '[') ignoreLastBracket = true; + } else { + // + char lookAhead = lookAhead(pattern, i); + if (lookAhead != '\\' && lookAhead != '[' + && lookAhead != '?' && lookAhead != '*' + && lookAhead != ' ' && lookBehind(sb) != '\\') { + break; + } } sb.append(c); break; @@ -349,7 +394,16 @@ public class Strings { if (in_brackets > 0) throw new InvalidPatternException("Not closed bracket?", pattern); //$NON-NLS-1$ - return Pattern.compile(sb.toString()); + try { + return Pattern.compile(sb.toString()); + } catch (PatternSyntaxException e) { + InvalidPatternException patternException = new InvalidPatternException( + MessageFormat.format(JGitText.get().invalidIgnoreRule, + pattern), + pattern); + patternException.initCause(e); + throw patternException; + } } /** @@ -401,4 +455,30 @@ public class Strings { return null; } + static String deleteBackslash(String s) { + if (s.indexOf('\\') < 0) { + return s; + } + StringBuilder sb = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (ch == '\\') { + if (i + 1 == s.length()) { + continue; + } + char next = s.charAt(i + 1); + if (next == '\\') { + sb.append(ch); + i++; + continue; + } + if (!escapedByBackslash(next)) { + continue; + } + } + sb.append(ch); + } + return sb.toString(); + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java index 4a1c780d99..b927d27dbe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java @@ -50,7 +50,7 @@ package org.eclipse.jgit.ignore.internal; public class TrailingAsteriskMatcher extends NameMatcher { TrailingAsteriskMatcher(String pattern, Character pathSeparator, boolean dirOnly) { - super(pattern, pathSeparator, dirOnly); + super(pattern, pathSeparator, dirOnly, true); if (subPattern.charAt(subPattern.length() - 1) != '*') throw new IllegalArgumentException( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java index 7d12b0ddce..8f9815283d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java @@ -62,7 +62,7 @@ public class WildCardMatcher extends NameMatcher { WildCardMatcher(String pattern, Character pathSeparator, boolean dirOnly) throws InvalidPatternException { - super(pattern, pathSeparator, dirOnly); + super(pattern, pathSeparator, dirOnly, false); p = convertGlob(subPattern); } 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 9f6efef570..9067e82954 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -296,12 +296,10 @@ public class JGitText extends TranslationBundle { /***/ public String errorEncodingFromFile; /***/ public String errorInBase64CodeReadingStream; /***/ public String errorInPackedRefs; - /***/ public String errorInvalidPushCert; /***/ public String errorInvalidProtocolWantedOldNewRef; /***/ public String errorListing; /***/ public String errorOccurredDuringUnpackingOnTheRemoteEnd; /***/ public String errorReadingInfoRefs; - /***/ public String errorSymlinksNotSupported; /***/ public String exceptionCaughtDuringExecutionOfHook; /***/ public String exceptionCaughtDuringExecutionOfAddCommand; /***/ public String exceptionCaughtDuringExecutionOfArchiveCommand; @@ -347,7 +345,6 @@ public class JGitText extends TranslationBundle { /***/ public String gcFailed; /***/ public String gitmodulesNotFound; /***/ public String headRequiredToStash; - /***/ public String hiddenFilesStartWithDot; /***/ public String hoursAgo; /***/ public String hugeIndexesAreNotSupportedByJgitYet; /***/ public String hunkBelongsToAnotherFile; @@ -385,8 +382,10 @@ public class JGitText extends TranslationBundle { /***/ public String invalidGitdirRef; /***/ public String invalidGitType; /***/ public String invalidId; + /***/ public String invalidId0; /***/ public String invalidIdLength; /***/ public String invalidIgnoreParamSubmodule; + /***/ public String invalidIgnoreRule; /***/ public String invalidIntegerValue; /***/ public String invalidKey; /***/ public String invalidLineInConfigFile; @@ -403,6 +402,7 @@ public class JGitText extends TranslationBundle { /***/ public String invalidReflogRevision; /***/ public String invalidRefName; /***/ public String invalidRemote; + /***/ public String invalidShallowObject; /***/ public String invalidStageForPath; /***/ public String invalidTagOption; /***/ public String invalidTimeout; @@ -499,6 +499,7 @@ public class JGitText extends TranslationBundle { /***/ public String packChecksumMismatch; /***/ public String packCorruptedWhileWritingToFilesystem; /***/ public String packDoesNotMatchIndex; + /***/ public String packedRefsHandleIsStale; /***/ public String packetSizeMustBeAtLeast; /***/ public String packetSizeMustBeAtMost; /***/ public String packfileCorruptionDetected; @@ -532,6 +533,10 @@ public class JGitText extends TranslationBundle { /***/ public String pullOnRepoWithoutHEADCurrentlyNotSupported; /***/ public String pullTaskName; /***/ public String pushCancelled; + /***/ public String pushCertificateInvalidField; + /***/ public String pushCertificateInvalidFieldValue; + /***/ public String pushCertificateInvalidHeader; + /***/ public String pushCertificateInvalidSignature; /***/ public String pushIsNotSupportedForBundleTransport; /***/ public String pushNotPermitted; /***/ public String rawLogMessageDoesNotParseAsLogEntry; @@ -590,7 +595,6 @@ public class JGitText extends TranslationBundle { /***/ public String sequenceTooLargeForDiffAlgorithm; /***/ public String serviceNotEnabledNoName; /***/ public String serviceNotPermitted; - /***/ public String serviceNotPermittedNoName; /***/ public String shallowCommitsAlreadyInitialized; /***/ public String shortCompressedStreamAt; /***/ public String shortReadOfBlock; @@ -620,6 +624,9 @@ public class JGitText extends TranslationBundle { /***/ public String stashFailed; /***/ public String stashResolveFailed; /***/ public String statelessRPCRequiresOptionToBeEnabled; + /***/ public String storePushCertMultipleRefs; + /***/ public String storePushCertOneRef; + /***/ public String storePushCertReflog; /***/ public String submoduleExists; /***/ public String submodulesNotSupported; /***/ public String submoduleParentRemoteUrlInvalid; @@ -661,6 +668,7 @@ public class JGitText extends TranslationBundle { /***/ public String unableToCreateNewObject; /***/ public String unableToStore; /***/ public String unableToWrite; + /***/ public String unauthorized; /***/ public String unencodeableFile; /***/ public String unexpectedCompareResult; /***/ public String unexpectedEndOfConfigFile; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index de66292208..faf27e32bb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -72,6 +72,7 @@ import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.storage.pack.PackStatistics; import org.eclipse.jgit.util.io.CountingOutputStream; /** Repack and garbage collect a repository. */ @@ -84,7 +85,7 @@ public class DfsGarbageCollector { private final List<DfsPackDescription> newPackDesc; - private final List<PackWriter.Statistics> newPackStats; + private final List<PackStatistics> newPackStats; private final List<PackWriter.ObjectIdSet> newPackObj; @@ -115,7 +116,7 @@ public class DfsGarbageCollector { refdb = repo.getRefDatabase(); objdb = repo.getObjectDatabase(); newPackDesc = new ArrayList<DfsPackDescription>(4); - newPackStats = new ArrayList<PackWriter.Statistics>(4); + newPackStats = new ArrayList<PackStatistics>(4); newPackObj = new ArrayList<PackWriter.ObjectIdSet>(4); packConfig = new PackConfig(repo); @@ -258,7 +259,7 @@ public class DfsGarbageCollector { } /** @return statistics corresponding to the {@link #getNewPacks()}. */ - public List<PackWriter.Statistics> getNewPackStatistics() { + public List<PackStatistics> getNewPackStatistics() { return newPackStats; } @@ -396,7 +397,7 @@ public class DfsGarbageCollector { } }); - PackWriter.Statistics stats = pw.getStatistics(); + PackStatistics stats = pw.getStatistics(); pack.setPackStats(stats); newPackStats.add(stats); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java index dbe72b2d43..7073763a7a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java @@ -67,6 +67,7 @@ import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.storage.pack.PackStatistics; import org.eclipse.jgit.util.BlockList; import org.eclipse.jgit.util.io.CountingOutputStream; @@ -94,7 +95,7 @@ public class DfsPackCompactor { private final List<DfsPackDescription> newPacks; - private final List<PackWriter.Statistics> newStats; + private final List<PackStatistics> newStats; private int autoAddSize; @@ -114,7 +115,7 @@ public class DfsPackCompactor { srcPacks = new ArrayList<DfsPackFile>(); exclude = new ArrayList<PackWriter.ObjectIdSet>(4); newPacks = new ArrayList<DfsPackDescription>(1); - newStats = new ArrayList<PackWriter.Statistics>(1); + newStats = new ArrayList<PackStatistics>(1); } /** @@ -231,7 +232,7 @@ public class DfsPackCompactor { writePack(objdb, pack, pw, pm); writeIndex(objdb, pack, pw); - PackWriter.Statistics stats = pw.getStatistics(); + PackStatistics stats = pw.getStatistics(); pw.close(); pw = null; @@ -264,7 +265,7 @@ public class DfsPackCompactor { } /** @return statistics corresponding to the {@link #getNewPacks()}. */ - public List<PackWriter.Statistics> getNewPackStatistics() { + public List<PackStatistics> getNewPackStatistics() { return newStats; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java index fba5157942..2b9d0e55c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java @@ -50,7 +50,7 @@ import java.util.Map; import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.pack.PackExt; -import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.storage.pack.PackStatistics; /** * Description of a DFS stored pack/index file. @@ -75,7 +75,7 @@ public class DfsPackDescription implements Comparable<DfsPackDescription> { private long deltaCount; - private PackWriter.Statistics stats; + private PackStatistics stats; private int extensions; @@ -225,11 +225,11 @@ public class DfsPackDescription implements Comparable<DfsPackDescription> { * DfsGarbageCollector or DfsPackCompactor, and only when the pack * is being committed to the repository. */ - public PackWriter.Statistics getPackStats() { + public PackStatistics getPackStats() { return stats; } - DfsPackDescription setPackStats(PackWriter.Statistics stats) { + DfsPackDescription setPackStats(PackStatistics stats) { this.stats = stats; setFileSize(PACK, stats.getTotalBytes()); setObjectCount(stats.getTotalObjects()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java index 75b0646ed0..96f1d542ca 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java @@ -533,7 +533,6 @@ public final class DfsPackFile { return ByteBuffer.wrap(copyBuf, 0, bs); } - @SuppressWarnings("null") void copyAsIs(PackOutputStream out, DfsObjectToPack src, boolean validate, DfsReader ctx) throws IOException, StoredObjectRepresentationNotAvailableException { @@ -567,22 +566,26 @@ public final class DfsPackFile { c = buf[headerCnt++] & 0xff; } while ((c & 128) != 0); if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); } } else if (typeCode == Constants.OBJ_REF_DELTA) { if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); } readFully(src.offset + headerCnt, buf, 0, 20, ctx); if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, 20); crc2.update(buf, 0, 20); } headerCnt += 20; } else if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); } @@ -599,6 +602,7 @@ public final class DfsPackFile { quickCopy = ctx.quickCopy(this, dataOffset, dataLength); if (validate && idx(ctx).hasCRC32Support()) { + assert(crc1 != null); // Index has the CRC32 code cached, validate the object. // expectedCRC = idx(ctx).findCRC32(src); @@ -622,6 +626,7 @@ public final class DfsPackFile { Long.valueOf(src.offset), getPackName())); } } else if (validate) { + assert(crc1 != null); // We don't have a CRC32 code in the index, so compute it // now while inflating the raw data to get zlib to tell us // whether or not the data is safe. @@ -709,16 +714,21 @@ public final class DfsPackFile { while (cnt > 0) { final int n = (int) Math.min(cnt, buf.length); readFully(pos, buf, 0, n, ctx); - if (validate) + if (validate) { + assert(crc2 != null); crc2.update(buf, 0, n); + } out.write(buf, 0, n); pos += n; cnt -= n; } - if (validate && crc2.getValue() != expectedCRC) { - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackName())); + if (validate) { + assert(crc2 != null); + if (crc2.getValue() != expectedCRC) { + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + Long.valueOf(src.offset), getPackName())); + } } } } @@ -837,7 +847,6 @@ public final class DfsPackFile { return buf.position(); } - @SuppressWarnings("null") ObjectLoader load(DfsReader ctx, long pos) throws IOException { try { @@ -934,6 +943,7 @@ public final class DfsPackFile { if (data == null) throw new LargeObjectException(); + assert(delta != null); do { // Cache only the base immediately before desired object. if (cached) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java index ed8acd578f..a1035a1284 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java @@ -91,6 +91,13 @@ public abstract class DfsRefDatabase extends RefDatabase { } @Override + public Ref exactRef(String name) throws IOException { + RefCache curr = read(); + Ref ref = curr.ids.get(name); + return ref != null ? resolve(ref, 0, curr.ids) : null; + } + + @Override public Ref getRef(String needle) throws IOException { RefCache curr = read(); for (String prefix : SEARCH_PATH) { @@ -103,14 +110,6 @@ public abstract class DfsRefDatabase extends RefDatabase { return null; } - private Ref getOneRef(String refName) throws IOException { - RefCache curr = read(); - Ref ref = curr.ids.get(refName); - if (ref != null) - return resolve(ref, 0, curr.ids); - return ref; - } - @Override public List<Ref> getAdditionalRefs() { return Collections.emptyList(); @@ -212,7 +211,7 @@ public abstract class DfsRefDatabase extends RefDatabase { public RefUpdate newUpdate(String refName, boolean detach) throws IOException { boolean detachingSymbolicRef = false; - Ref ref = getOneRef(refName); + Ref ref = exactRef(refName); if (ref == null) ref = new ObjectIdRef.Unpeeled(NEW, refName, null); else diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java index 796109aee2..e7ef127dd7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java @@ -114,8 +114,6 @@ public class ObjectDirectory extends FileObjectDatabase { /** Maximum number of candidates offered as resolutions of abbreviation. */ private static final int RESOLVE_ABBREV_LIMIT = 256; - private static final String STALE_FILE_HANDLE_MSG = "stale file handle"; //$NON-NLS-1$ - private final Config config; private final File objects; @@ -565,8 +563,7 @@ public class ObjectDirectory extends FileObjectDatabase { } else if (e instanceof FileNotFoundException) { warnTmpl = JGitText.get().packWasDeleted; removePack(p); - } else if (e.getMessage() != null - && e.getMessage().toLowerCase().contains(STALE_FILE_HANDLE_MSG)) { + } else if (FileUtils.isStaleFileHandle(e)) { warnTmpl = JGitText.get().packHandleIsStale; removePack(p); } @@ -602,7 +599,7 @@ public class ObjectDirectory extends FileObjectDatabase { } final File dst = fileFor(id); - if (fs.exists(dst)) { + if (dst.exists()) { // We want to be extra careful and avoid replacing an object // that already exists. We can't be sure renameTo() would // fail on all platforms if dst exists, so we check first. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java index 05cd100953..93f891864a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java @@ -44,7 +44,7 @@ package org.eclipse.jgit.internal.storage.file; import java.text.MessageFormat; -import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -73,7 +73,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { private final EWAHCompressedBitmap trees; private final EWAHCompressedBitmap blobs; private final EWAHCompressedBitmap tags; - private final ObjectToPack[] byOffset; + private final BlockList<PositionEntry> byOffset; private final BlockList<StoredBitmap> byAddOrder = new BlockList<StoredBitmap>(); private final ObjectIdOwnerMap<PositionEntry> @@ -83,20 +83,25 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { * Creates a PackBitmapIndex used for building the contents of an index * file. * - * @param byName - * objects sorted by name. + * @param objects + * objects sorted by name. The list must be initially sorted by + * ObjectId (name); it will be resorted in place. */ - public PackBitmapIndexBuilder(List<ObjectToPack> byName) { + public PackBitmapIndexBuilder(List<ObjectToPack> objects) { super(new ObjectIdOwnerMap<StoredBitmap>()); - byOffset = sortByOffset(byName); + byOffset = new BlockList<>(objects.size()); + sortByOffsetAndIndex(byOffset, positionEntries, objects); - int sizeInWords = Math.max(byOffset.length / 64, 4); + // 64 objects fit in a single long word (64 bits). + // On average a repository is 30% commits, 30% trees, 30% blobs. + // Initialize bitmap capacity for worst case to minimize growing. + int sizeInWords = Math.max(4, byOffset.size() / 64 / 3); commits = new EWAHCompressedBitmap(sizeInWords); trees = new EWAHCompressedBitmap(sizeInWords); blobs = new EWAHCompressedBitmap(sizeInWords); tags = new EWAHCompressedBitmap(sizeInWords); - for (int i = 0; i < byOffset.length; i++) { - int type = byOffset[i].getType(); + for (int i = 0; i < objects.size(); i++) { + int type = objects.get(i).getType(); switch (type) { case Constants.OBJ_COMMIT: commits.set(i); @@ -115,22 +120,39 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { JGitText.get().badObjectType, String.valueOf(type))); } } + commits.trim(); + trees.trim(); + blobs.trim(); + tags.trim(); } - private ObjectToPack[] sortByOffset(List<ObjectToPack> entries) { - ObjectToPack[] result = new ObjectToPack[entries.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = entries.get(i); - positionEntries.add(new PositionEntry(result[i], i)); + private static void sortByOffsetAndIndex(BlockList<PositionEntry> byOffset, + ObjectIdOwnerMap<PositionEntry> positionEntries, + List<ObjectToPack> entries) { + for (int i = 0; i < entries.size(); i++) { + positionEntries.add(new PositionEntry(entries.get(i), i)); } - Arrays.sort(result, new Comparator<ObjectToPack>() { + Collections.sort(entries, new Comparator<ObjectToPack>() { public int compare(ObjectToPack a, ObjectToPack b) { return Long.signum(a.getOffset() - b.getOffset()); } }); - for (int i = 0; i < result.length; i++) - positionEntries.get(result[i]).offsetPosition = i; - return result; + for (int i = 0; i < entries.size(); i++) { + PositionEntry e = positionEntries.get(entries.get(i)); + e.offsetPosition = i; + byOffset.add(e); + } + } + + /** @return set of objects included in the pack. */ + public ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> getObjectSet() { + ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> r = new ObjectIdOwnerMap<>(); + for (PositionEntry e : byOffset) { + r.add(new ObjectIdOwnerMap.Entry(e) { + // A new entry that copies the ObjectId + }); + } + return r; } /** @@ -168,6 +190,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { */ public void addBitmap( AnyObjectId objectId, EWAHCompressedBitmap bitmap, int flags) { + bitmap.trim(); StoredBitmap result = new StoredBitmap(objectId, bitmap, null, flags); getBitmaps().add(result); byAddOrder.add(result); @@ -199,7 +222,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { @Override public ObjectId getObject(int position) throws IllegalArgumentException { - ObjectId objectId = byOffset[position]; + ObjectId objectId = byOffset.get(position); if (objectId == null) throw new IllegalArgumentException(); return objectId; @@ -243,7 +266,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { @Override public int getObjectCount() { - return byOffset.length; + return byOffset.size(); } /** @return an iterator over the xor compressed entries. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java index 6d944fd4db..a38a26dec0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java @@ -60,8 +60,7 @@ import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.NB; /** - * Support for the pack bitmap index v1 format, which contains experimental - * support for bitmaps. + * Support for the pack bitmap index v1 format. * * @see PackBitmapIndex */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java index b29966eace..589a811735 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java @@ -366,7 +366,6 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { } } - @SuppressWarnings("null") private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, boolean validate, WindowCursor curs) throws IOException, StoredObjectRepresentationNotAvailableException { @@ -393,22 +392,26 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { c = buf[headerCnt++] & 0xff; } while ((c & 128) != 0); if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); } } else if (typeCode == Constants.OBJ_REF_DELTA) { if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); } readFully(src.offset + headerCnt, buf, 0, 20, curs); if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, 20); crc2.update(buf, 0, 20); } headerCnt += 20; } else if (validate) { + assert(crc1 != null && crc2 != null); crc1.update(buf, 0, headerCnt); crc2.update(buf, 0, headerCnt); } @@ -425,6 +428,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { quickCopy = curs.quickCopy(this, dataOffset, dataLength); if (validate && idx().hasCRC32Support()) { + assert(crc1 != null); // Index has the CRC32 code cached, validate the object. // expectedCRC = idx().findCRC32(src); @@ -457,6 +461,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { if (quickCopy != null) { quickCopy.check(inf, tmp, dataOffset, (int) dataLength); } else { + assert(crc1 != null); long pos = dataOffset; long cnt = dataLength; while (cnt > 0) { @@ -476,6 +481,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { JGitText.get().shortCompressedStreamAt, Long.valueOf(src.offset))); } + assert(crc1 != null); expectedCRC = crc1.getValue(); } else { expectedCRC = -1; @@ -535,16 +541,21 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { while (cnt > 0) { final int n = (int) Math.min(cnt, buf.length); readFully(pos, buf, 0, n, curs); - if (validate) + if (validate) { + assert(crc2 != null); crc2.update(buf, 0, n); + } out.write(buf, 0, n); pos += n; cnt -= n; } - if (validate && crc2.getValue() != expectedCRC) { - throw new CorruptObjectException(MessageFormat.format( - JGitText.get().objectAtHasBadZlibStream, - Long.valueOf(src.offset), getPackFile())); + if (validate) { + assert(crc2 != null); + if (crc2.getValue() != expectedCRC) { + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().objectAtHasBadZlibStream, + Long.valueOf(src.offset), getPackFile())); + } } } } @@ -712,7 +723,6 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { , getPackFile())); } - @SuppressWarnings("null") ObjectLoader load(final WindowCursor curs, long pos) throws IOException, LargeObjectException { try { @@ -811,6 +821,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { if (data == null) throw new IOException(JGitText.get().inMemoryBufferLimitExceeded); + assert(delta != null); do { // Cache only the base immediately before desired object. if (cached) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java index 6d6d0cabd0..bb5b044405 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java @@ -98,6 +98,8 @@ import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.RefList; import org.eclipse.jgit.util.RefMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Traditional file system based {@link RefDatabase}. @@ -115,6 +117,9 @@ import org.eclipse.jgit.util.RefMap; * overall size of a Git repository on disk. */ public class RefDirectory extends RefDatabase { + private final static Logger LOG = LoggerFactory + .getLogger(RefDirectory.class); + /** Magic string denoting the start of a symbolic reference file. */ public static final String SYMREF = "ref: "; //$NON-NLS-1$ @@ -257,6 +262,30 @@ public class RefDirectory extends RefDatabase { } @Override + public Ref exactRef(String name) throws IOException { + RefList<Ref> packed = getPackedRefs(); + Ref ref; + try { + ref = readRef(name, packed); + if (ref != null) { + ref = resolve(ref, 0, null, null, packed); + } + } catch (IOException e) { + if (name.contains("/") //$NON-NLS-1$ + || !(e.getCause() instanceof InvalidObjectIdException)) { + throw e; + } + + // While looking for a ref outside of refs/ (e.g., 'config'), we + // found a non-ref file (e.g., a config file) instead. Treat this + // as a ref-not-found condition. + ref = null; + } + fireRefsChanged(); + return ref; + } + + @Override public Ref getRef(final String needle) throws IOException { final RefList<Ref> packed = getPackedRefs(); Ref ref = null; @@ -746,22 +775,37 @@ public class RefDirectory extends RefDatabase { } private PackedRefList readPackedRefs() throws IOException { - final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile); - final BufferedReader br; - final MessageDigest digest = Constants.newMessageDigest(); - try { - br = new BufferedReader(new InputStreamReader( - new DigestInputStream(new FileInputStream(packedRefsFile), - digest), CHARSET)); - } catch (FileNotFoundException noPackedRefs) { - // Ignore it and leave the new list empty. - return PackedRefList.NO_PACKED_REFS; - } - try { - return new PackedRefList(parsePackedRefs(br), snapshot, - ObjectId.fromRaw(digest.digest())); - } finally { - br.close(); + int maxStaleRetries = 5; + int retries = 0; + while (true) { + final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile); + final BufferedReader br; + final MessageDigest digest = Constants.newMessageDigest(); + try { + br = new BufferedReader(new InputStreamReader( + new DigestInputStream(new FileInputStream(packedRefsFile), + digest), CHARSET)); + } catch (FileNotFoundException noPackedRefs) { + // Ignore it and leave the new list empty. + return PackedRefList.NO_PACKED_REFS; + } + try { + return new PackedRefList(parsePackedRefs(br), snapshot, + ObjectId.fromRaw(digest.digest())); + } catch (IOException e) { + if (FileUtils.isStaleFileHandle(e) && retries < maxStaleRetries) { + if (LOG.isDebugEnabled()) { + LOG.debug(MessageFormat.format( + JGitText.get().packedRefsHandleIsStale, + Integer.valueOf(retries)), e); + } + retries++; + continue; + } + throw e; + } finally { + br.close(); + } } } @@ -881,7 +925,6 @@ public class RefDirectory extends RefDatabase { return n; } - @SuppressWarnings("null") private LooseRef scanRef(LooseRef ref, String name) throws IOException { final File path = fileFor(name); FileSnapshot currentSnapshot = null; @@ -920,6 +963,7 @@ public class RefDirectory extends RefDatabase { final String target = RawParseUtils.decode(buf, 5, n); if (ref != null && ref.isSymbolic() && ref.getTarget().getName().equals(target)) { + assert(currentSnapshot != null); currentSnapshot.setClean(otherSnapshot); return ref; } @@ -934,6 +978,7 @@ public class RefDirectory extends RefDatabase { id = ObjectId.fromString(buf, 0); if (ref != null && !ref.isSymbolic() && ref.getTarget().getObjectId().equals(id)) { + assert(currentSnapshot != null); currentSnapshot.setClean(otherSnapshot); return ref; } 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 adc6bf11ab..683d1cd6e9 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 @@ -114,6 +114,9 @@ import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.storage.pack.PackStatistics; +import org.eclipse.jgit.transport.ObjectCountCallback; +import org.eclipse.jgit.transport.WriteAbortedException; import org.eclipse.jgit.util.BlockList; import org.eclipse.jgit.util.TemporaryBuffer; @@ -133,12 +136,14 @@ import org.eclipse.jgit.util.TemporaryBuffer; * order of objects in pack</li> * </ul> * <p> - * Typical usage consists of creating instance intended for some pack, - * configuring options, preparing the list of objects by calling - * {@link #preparePack(Iterator)} or - * {@link #preparePack(ProgressMonitor, Set, Set)}, and finally producing the - * stream with - * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. + * Typical usage consists of creating an instance, configuring options, + * preparing the list of objects by calling {@link #preparePack(Iterator)} or + * {@link #preparePack(ProgressMonitor, Set, Set)}, and streaming with + * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. If the + * pack is being stored as a file the matching index can be written out after + * writing the pack by {@link #writeIndex(OutputStream)}. An optional bitmap + * index can be made by calling {@link #prepareBitmapIndex(ProgressMonitor)} + * followed by {@link #writeBitmapIndex(OutputStream)}. * </p> * <p> * Class provide set of configurable options and {@link ProgressMonitor} @@ -147,9 +152,10 @@ import org.eclipse.jgit.util.TemporaryBuffer; * relies only on deltas and objects reuse. * </p> * <p> - * This class is not thread safe, it is intended to be used in one thread, with - * one instance per created pack. Subsequent calls to writePack result in - * undefined behavior. + * This class is not thread safe. It is intended to be used in one thread as a + * single pass to produce one pack. Invoking methods multiple times or out of + * order is not supported as internal data structures are destroyed during + * certain phases to save memory when packing large repositories. * </p> */ public class PackWriter implements AutoCloseable { @@ -212,7 +218,7 @@ public class PackWriter implements AutoCloseable { } @SuppressWarnings("unchecked") - private final BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1]; + private BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1]; { objectsLists[OBJ_COMMIT] = new BlockList<ObjectToPack>(); objectsLists[OBJ_TREE] = new BlockList<ObjectToPack>(); @@ -220,7 +226,7 @@ public class PackWriter implements AutoCloseable { objectsLists[OBJ_TAG] = new BlockList<ObjectToPack>(); } - private final ObjectIdOwnerMap<ObjectToPack> objectsMap = new ObjectIdOwnerMap<ObjectToPack>(); + private ObjectIdOwnerMap<ObjectToPack> objectsMap = new ObjectIdOwnerMap<ObjectToPack>(); // edge objects for thin packs private List<ObjectToPack> edgeObjects = new BlockList<ObjectToPack>(); @@ -245,13 +251,13 @@ public class PackWriter implements AutoCloseable { private final PackConfig config; - private final Statistics stats; + private final PackStatistics.Accumulator stats; private final MutableState state; private final WeakReference<PackWriter> selfRef; - private Statistics.ObjectType typeStats; + private PackStatistics.ObjectType.Accumulator typeStats; private List<ObjectToPack> sortedByName; @@ -289,6 +295,8 @@ public class PackWriter implements AutoCloseable { private CRC32 crc32; + private ObjectCountCallback callback; + /** * Create writer for specified repository. * <p> @@ -352,13 +360,42 @@ public class PackWriter implements AutoCloseable { deltaBaseAsOffset = config.isDeltaBaseAsOffset(); reuseDeltas = config.isReuseDeltas(); reuseValidate = true; // be paranoid by default - stats = new Statistics(); + stats = new PackStatistics.Accumulator(); state = new MutableState(); selfRef = new WeakReference<PackWriter>(this); instances.put(selfRef, Boolean.TRUE); } /** + * Set the {@code ObjectCountCallback}. + * <p> + * It should be set before calling + * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. + * + * @param callback + * the callback to set + * + * @return this object for chaining. + * @since 4.1 + */ + public PackWriter setObjectCountCallback(ObjectCountCallback callback) { + this.callback = callback; + return this; + } + + /** + * Records the set of shallow commits in the client. + * + * @param clientShallowCommits + * the shallow commits in the client + * @since 4.1 + */ + public void setClientShallowCommits(Set<ObjectId> clientShallowCommits) { + stats.clientShallowCommits = Collections + .unmodifiableSet(new HashSet<ObjectId>(clientShallowCommits)); + } + + /** * Check whether writer can store delta base as an offset (new style * reducing pack size) or should store it as an object id (legacy style, * compatible with old readers). @@ -571,12 +608,12 @@ public class PackWriter implements AutoCloseable { /** * Returns the object ids in the pack file that was created by this writer. - * + * <p> * This method can only be invoked after * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)} has * been invoked and completed successfully. * - * @return number of objects in pack. + * @return set of objects in pack. * @throws IOException * a cached pack cannot supply its object ids. */ @@ -586,17 +623,20 @@ public class PackWriter implements AutoCloseable { throw new IOException( JGitText.get().cachedPacksPreventsListingObjects); - ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> objs = new ObjectIdOwnerMap< - ObjectIdOwnerMap.Entry>(); + if (writeBitmaps != null) { + return writeBitmaps.getObjectSet(); + } + + ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> r = new ObjectIdOwnerMap<>(); for (BlockList<ObjectToPack> objList : objectsLists) { if (objList != null) { for (ObjectToPack otp : objList) - objs.add(new ObjectIdOwnerMap.Entry(otp) { + r.add(new ObjectIdOwnerMap.Entry(otp) { // A new entry that copies the ObjectId }); } } - return objs; + return r; } /** @@ -784,10 +824,11 @@ public class PackWriter implements AutoCloseable { /** * Create an index file to match the pack file just written. * <p> - * This method can only be invoked after - * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)} has - * been invoked and completed successfully. Writing a corresponding index is - * an optional feature that not all pack users may require. + * Called after + * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. + * <p> + * Writing an index is only required for local pack storage. Packs sent on + * the network do not need to create an index. * * @param indexStream * output for the index data. Caller is responsible for closing @@ -809,10 +850,7 @@ public class PackWriter implements AutoCloseable { /** * Create a bitmap index file to match the pack file just written. * <p> - * This method can only be invoked after - * {@link #prepareBitmapIndex(ProgressMonitor)} has been invoked and - * completed successfully. Writing a corresponding bitmap index is an - * optional feature that not all pack users may require. + * Called after {@link #prepareBitmapIndex(ProgressMonitor)}. * * @param bitmapIndexStream * output for the bitmap index data. Caller is responsible for @@ -886,14 +924,13 @@ public class PackWriter implements AutoCloseable { /** * Write the prepared pack to the supplied stream. * <p> - * At first, this method collects and sorts objects to pack, then deltas - * search is performed if set up accordingly, finally pack stream is - * written. - * </p> + * Called after {@link #preparePack(ProgressMonitor, ObjectWalk, Set, Set)} + * or {@link #preparePack(ProgressMonitor, Set, Set)}. + * <p> + * Performs delta search if enabled and writes the pack stream. * <p> * All reused objects data checksum (Adler32/CRC32) is computed and * validated against existing checksum. - * </p> * * @param compressMonitor * progress monitor to report object compression work. @@ -906,6 +943,9 @@ public class PackWriter implements AutoCloseable { * an error occurred reading a local object's data to include in * the pack, or writing compressed object data to the output * stream. + * @throws WriteAbortedException + * the write operation is aborted by {@link ObjectCountCallback} + * . */ public void writePack(ProgressMonitor compressMonitor, ProgressMonitor writeMonitor, OutputStream packStream) @@ -947,6 +987,8 @@ public class PackWriter implements AutoCloseable { long objCnt = getObjectCount(); stats.totalObjects = objCnt; + if (callback != null) + callback.setObjectCount(objCnt); beginPhase(PackingPhase.WRITING, writeMonitor, objCnt); long writeStart = System.currentTimeMillis(); try { @@ -955,7 +997,7 @@ public class PackWriter implements AutoCloseable { writeObjects(out); if (!edgeObjects.isEmpty() || !cachedPacks.isEmpty()) { - for (Statistics.ObjectType typeStat : stats.objectTypes) { + for (PackStatistics.ObjectType.Accumulator typeStat : stats.objectTypes) { if (typeStat == null) continue; stats.thinPackBytes += typeStat.bytes; @@ -976,7 +1018,7 @@ public class PackWriter implements AutoCloseable { stats.timeWriting = System.currentTimeMillis() - writeStart; stats.depth = depth; - for (Statistics.ObjectType typeStat : stats.objectTypes) { + for (PackStatistics.ObjectType.Accumulator typeStat : stats.objectTypes) { if (typeStat == null) continue; typeStat.cntDeltas += typeStat.reusedDeltas; @@ -993,11 +1035,11 @@ public class PackWriter implements AutoCloseable { /** * @return description of what this PackWriter did in order to create the - * final pack stream. The object is only available to callers after - * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)} + * final pack stream. This should only be invoked after the calls to + * create the pack/index/bitmap have completed. */ - public Statistics getStatistics() { - return stats; + public PackStatistics getStatistics() { + return new PackStatistics(stats); } /** @return snapshot of the current state of this PackWriter. */ @@ -1676,6 +1718,7 @@ public class PackWriter implements AutoCloseable { final int maxBases = config.getDeltaSearchWindowSize(); Set<RevTree> baseTrees = new HashSet<RevTree>(); BlockList<RevCommit> commits = new BlockList<RevCommit>(); + Set<ObjectId> roots = new HashSet<>(); RevCommit c; while ((c = walker.next()) != null) { if (exclude(c)) @@ -1687,8 +1730,12 @@ public class PackWriter implements AutoCloseable { } commits.add(c); + if (c.getParentCount() == 0) { + roots.add(c.copy()); + } countingMonitor.update(1); } + stats.rootCommits = Collections.unmodifiableSet(roots); if (shallowPack) { for (RevCommit cmit : commits) { @@ -1764,6 +1811,7 @@ public class PackWriter implements AutoCloseable { countingMonitor.update((int) pack.getObjectCount()); endPhase(countingMonitor); stats.timeCounting = System.currentTimeMillis() - countingStart; + stats.bitmapIndexMisses = -1; } private void findObjectsToPackUsingBitmaps( @@ -1932,14 +1980,17 @@ public class PackWriter implements AutoCloseable { } /** - * Prepares the bitmaps to be written to the pack index. Bitmaps can be used - * to speed up fetches and clones by storing the entire object graph at - * selected commits. - * - * This method can only be invoked after - * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)} has - * been invoked and completed successfully. Writing a corresponding bitmap - * index is an optional feature that not all pack users may require. + * Prepares the bitmaps to be written to the bitmap index file. + * <p> + * Bitmaps can be used to speed up fetches and clones by storing the entire + * object graph at selected commits. Writing a bitmap index is an optional + * feature that not all pack users may require. + * <p> + * Called after {@link #writeIndex(OutputStream)}. + * <p> + * To reduce memory internal state is cleared during this method, rendering + * the PackWriter instance useless for anything further than a call to write + * out the new bitmaps with {@link #writeBitmapIndex(OutputStream)}. * * @param pm * progress monitor to report bitmap building work. @@ -1955,11 +2006,17 @@ public class PackWriter implements AutoCloseable { if (pm == null) pm = NullProgressMonitor.INSTANCE; - writeBitmaps = new PackBitmapIndexBuilder(sortByName()); + int numCommits = objectsLists[OBJ_COMMIT].size(); + List<ObjectToPack> byName = sortByName(); + sortedByName = null; + objectsLists = null; + objectsMap = null; + writeBitmaps = new PackBitmapIndexBuilder(byName); + byName = null; + PackWriterBitmapPreparer bitmapPreparer = new PackWriterBitmapPreparer( reader, writeBitmaps, pm, stats.interestingObjects); - int numCommits = objectsLists[OBJ_COMMIT].size(); Collection<PackWriterBitmapPreparer.BitmapCommit> selectedCommits = bitmapPreparer.doCommitSelection(numCommits); @@ -2001,28 +2058,36 @@ public class PackWriter implements AutoCloseable { return true; } - /** Summary of how PackWriter created the pack. */ + /** + * Summary of how PackWriter created the pack. + * + * @deprecated Use {@link PackStatistics} instead. + */ + @Deprecated public static class Statistics { /** Statistics about a single class of object. */ public static class ObjectType { - long cntObjects; - - long cntDeltas; - - long reusedObjects; - - long reusedDeltas; + // All requests are forwarded to this object. + private PackStatistics.ObjectType objectType; - long bytes; - - long deltaBytes; + /** + * Wraps an + * {@link org.eclipse.jgit.storage.pack.PackStatistics.ObjectType} + * instance to maintain backwards compatibility with existing API. + * + * @param type + * the wrapped instance + */ + public ObjectType(PackStatistics.ObjectType type) { + objectType = type; + } /** * @return total number of objects output. This total includes the * value of {@link #getDeltas()}. */ public long getObjects() { - return cntObjects; + return objectType.getObjects(); } /** @@ -2030,7 +2095,7 @@ public class PackWriter implements AutoCloseable { * actual number of deltas if a cached pack was reused. */ public long getDeltas() { - return cntDeltas; + return objectType.getDeltas(); } /** @@ -2039,7 +2104,7 @@ public class PackWriter implements AutoCloseable { * {@link #getReusedDeltas()}. */ public long getReusedObjects() { - return reusedObjects; + return objectType.getReusedObjects(); } /** @@ -2050,7 +2115,7 @@ public class PackWriter implements AutoCloseable { * was reused. */ public long getReusedDeltas() { - return reusedDeltas; + return objectType.getReusedDeltas(); } /** @@ -2059,7 +2124,7 @@ public class PackWriter implements AutoCloseable { * also includes all of {@link #getDeltaBytes()}. */ public long getBytes() { - return bytes; + return objectType.getBytes(); } /** @@ -2067,54 +2132,22 @@ public class PackWriter implements AutoCloseable { * object headers for the delta objects. */ public long getDeltaBytes() { - return deltaBytes; + return objectType.getDeltaBytes(); } } - Set<ObjectId> interestingObjects; - - Set<ObjectId> uninterestingObjects; - - Collection<CachedPack> reusedPacks; - - int depth; - - int deltaSearchNonEdgeObjects; - - int deltasFound; - - long totalObjects; - - long bitmapIndexMisses; - - long totalDeltas; - - long reusedObjects; + // All requests are forwarded to this object. + private PackStatistics statistics; - long reusedDeltas; - - long totalBytes; - - long thinPackBytes; - - long timeCounting; - - long timeSearchingForReuse; - - long timeSearchingForSizes; - - long timeCompressing; - - long timeWriting; - - ObjectType[] objectTypes; - - { - objectTypes = new ObjectType[5]; - objectTypes[OBJ_COMMIT] = new ObjectType(); - objectTypes[OBJ_TREE] = new ObjectType(); - objectTypes[OBJ_BLOB] = new ObjectType(); - objectTypes[OBJ_TAG] = new ObjectType(); + /** + * Wraps a {@link PackStatistics} object to maintain backwards + * compatibility with existing API. + * + * @param stats + * the wrapped PackStatitics object + */ + public Statistics(PackStatistics stats) { + statistics = stats; } /** @@ -2123,7 +2156,7 @@ public class PackWriter implements AutoCloseable { * test. */ public Set<ObjectId> getInterestingObjects() { - return interestingObjects; + return statistics.getInterestingObjects(); } /** @@ -2132,7 +2165,7 @@ public class PackWriter implements AutoCloseable { * has these objects. */ public Set<ObjectId> getUninterestingObjects() { - return uninterestingObjects; + return statistics.getUninterestingObjects(); } /** @@ -2140,7 +2173,7 @@ public class PackWriter implements AutoCloseable { * in the output, if any were selected for reuse. */ public Collection<CachedPack> getReusedPacks() { - return reusedPacks; + return statistics.getReusedPacks(); } /** @@ -2148,7 +2181,7 @@ public class PackWriter implements AutoCloseable { * delta search process in order to find a potential delta base. */ public int getDeltaSearchNonEdgeObjects() { - return deltaSearchNonEdgeObjects; + return statistics.getDeltaSearchNonEdgeObjects(); } /** @@ -2157,7 +2190,7 @@ public class PackWriter implements AutoCloseable { * {@link #getDeltaSearchNonEdgeObjects()}. */ public int getDeltasFound() { - return deltasFound; + return statistics.getDeltasFound(); } /** @@ -2165,17 +2198,18 @@ public class PackWriter implements AutoCloseable { * of {@link #getTotalDeltas()}. */ public long getTotalObjects() { - return totalObjects; + return statistics.getTotalObjects(); } /** * @return the count of objects that needed to be discovered through an * object walk because they were not found in bitmap indices. + * Returns -1 if no bitmap indices were found. * * @since 4.0 */ public long getBitmapIndexMisses() { - return bitmapIndexMisses; + return statistics.getBitmapIndexMisses(); } /** @@ -2183,7 +2217,7 @@ public class PackWriter implements AutoCloseable { * actual number of deltas if a cached pack was reused. */ public long getTotalDeltas() { - return totalDeltas; + return statistics.getTotalDeltas(); } /** @@ -2191,7 +2225,7 @@ public class PackWriter implements AutoCloseable { * the output. This count includes {@link #getReusedDeltas()}. */ public long getReusedObjects() { - return reusedObjects; + return statistics.getReusedObjects(); } /** @@ -2201,7 +2235,7 @@ public class PackWriter implements AutoCloseable { * actual number of reused deltas if a cached pack was reused. */ public long getReusedDeltas() { - return reusedDeltas; + return statistics.getReusedDeltas(); } /** @@ -2209,7 +2243,7 @@ public class PackWriter implements AutoCloseable { * header, trailer, thin pack, and reused cached pack(s). */ public long getTotalBytes() { - return totalBytes; + return statistics.getTotalBytes(); } /** @@ -2221,7 +2255,7 @@ public class PackWriter implements AutoCloseable { * pack header or trailer. */ public long getThinPackBytes() { - return thinPackBytes; + return statistics.getThinPackBytes(); } /** @@ -2230,17 +2264,17 @@ public class PackWriter implements AutoCloseable { * @return information about this type of object in the pack. */ public ObjectType byObjectType(int typeCode) { - return objectTypes[typeCode]; + return new ObjectType(statistics.byObjectType(typeCode)); } /** @return true if the resulting pack file was a shallow pack. */ public boolean isShallow() { - return depth > 0; + return statistics.isShallow(); } /** @return depth (in commits) the pack includes if shallow. */ public int getDepth() { - return depth; + return statistics.getDepth(); } /** @@ -2249,7 +2283,7 @@ public class PackWriter implements AutoCloseable { * that occur when a cached pack is selected for reuse. */ public long getTimeCounting() { - return timeCounting; + return statistics.getTimeCounting(); } /** @@ -2258,7 +2292,7 @@ public class PackWriter implements AutoCloseable { * can be assumed to already have. */ public long getTimeSearchingForReuse() { - return timeSearchingForReuse; + return statistics.getTimeSearchingForReuse(); } /** @@ -2268,7 +2302,7 @@ public class PackWriter implements AutoCloseable { * together and improve delta compression ratios. */ public long getTimeSearchingForSizes() { - return timeSearchingForSizes; + return statistics.getTimeSearchingForSizes(); } /** @@ -2278,7 +2312,7 @@ public class PackWriter implements AutoCloseable { * delta compression. */ public long getTimeCompressing() { - return timeCompressing; + return statistics.getTimeCompressing(); } /** @@ -2288,16 +2322,12 @@ public class PackWriter implements AutoCloseable { * value. */ public long getTimeWriting() { - return timeWriting; + return statistics.getTimeWriting(); } /** @return total time spent processing this pack. */ public long getTimeTotal() { - return timeCounting - + timeSearchingForReuse - + timeSearchingForSizes - + timeCompressing - + timeWriting; + return statistics.getTimeTotal(); } /** @@ -2305,14 +2335,12 @@ public class PackWriter implements AutoCloseable { * {@code getTotalBytes() / (getTimeWriting() / 1000.0)}. */ public double getTransferRate() { - return getTotalBytes() / (getTimeWriting() / 1000.0); + return statistics.getTransferRate(); } /** @return formatted message string for display to clients. */ public String getMessage() { - return MessageFormat.format(JGitText.get().packWriterStatistics, // - Long.valueOf(totalObjects), Long.valueOf(totalDeltas), // - Long.valueOf(reusedObjects), Long.valueOf(reusedDeltas)); + return statistics.getMessage(); } } @@ -2345,11 +2373,14 @@ public class PackWriter implements AutoCloseable { State snapshot() { long objCnt = 0; - objCnt += objectsLists[OBJ_COMMIT].size(); - objCnt += objectsLists[OBJ_TREE].size(); - objCnt += objectsLists[OBJ_BLOB].size(); - objCnt += objectsLists[OBJ_TAG].size(); - // Exclude CachedPacks. + BlockList<ObjectToPack>[] lists = objectsLists; + if (lists != null) { + objCnt += lists[OBJ_COMMIT].size(); + objCnt += lists[OBJ_TREE].size(); + objCnt += lists[OBJ_BLOB].size(); + objCnt += lists[OBJ_TAG].size(); + // Exclude CachedPacks. + } long bytesUsed = OBJECT_TO_PACK_SIZE * objCnt; PackingPhase curr = phase; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java index fca90636da..756d4b0005 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java @@ -114,6 +114,7 @@ class PackWriterBitmapPreparer { IOException { pm.beginTask(JGitText.get().selectingCommits, ProgressMonitor.UNKNOWN); RevWalk rw = new RevWalk(reader); + rw.setRetainBody(false); WalkResult result = findPaths(rw, expectedNumCommits); pm.endTask(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java index 73850b7889..d7e930831e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java @@ -59,6 +59,7 @@ import java.util.List; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.PushCertificate; import org.eclipse.jgit.transport.ReceiveCommand; /** @@ -85,6 +86,9 @@ public class BatchRefUpdate { /** Should the result value be appended to {@link #refLogMessage}. */ private boolean refLogIncludeResult; + /** Push certificate associated with this update. */ + private PushCertificate pushCert; + /** * Initialize a new batch update. * @@ -195,6 +199,33 @@ public class BatchRefUpdate { return refLogMessage == null; } + /** + * Set a push certificate associated with this update. + * <p> + * This usually includes commands to update the refs in this batch, but is not + * required to. + * + * @param cert + * push certificate, may be null. + * @since 4.1 + */ + public void setPushCertificate(PushCertificate cert) { + pushCert = cert; + } + + /** + * Set the push certificate associated with this update. + * <p> + * This usually includes commands to update the refs in this batch, but is not + * required to. + * + * @return push certificate, may be null. + * @since 4.1 + */ + protected PushCertificate getPushCertificate() { + return pushCert; + } + /** @return commands this update will process. */ public List<ReceiveCommand> getCommands() { return Collections.unmodifiableList(commands); @@ -377,6 +408,7 @@ public class BatchRefUpdate { ru.setRefLogIdent(refLogIdent); ru.setRefLogMessage(refLogMessage, refLogIncludeResult); } + ru.setPushCertificate(pushCert); switch (cmd.getType()) { case DELETE: if (!ObjectId.zeroId().equals(cmd.getOldId())) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index 22337e8131..e48386d024 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -1200,8 +1200,6 @@ public class Config { for (;;) { int c = in.read(); if (c < 0) { - if (value.length() == 0) - throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile); break; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index 33c65ab044..a7a67a8812 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -618,8 +618,9 @@ public class ObjectChecker { default: return false; } + default: + return false; } - break; case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024 checkTruncatedIgnorableUTF8(raw, ptr, end); // U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java index bdbffee490..4edb38c5df 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java @@ -45,7 +45,6 @@ package org.eclipse.jgit.lib; import org.eclipse.jgit.errors.InvalidObjectIdException; -import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.NB; import org.eclipse.jgit.util.RawParseUtils; @@ -53,7 +52,6 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.text.MessageFormat; /** * A SHA-1 abstraction. @@ -113,16 +111,16 @@ public class ObjectId extends AnyObjectId implements Serializable { } /** - * Compare to object identifier byte sequences for equality. + * Compare two object identifier byte sequences for equality. * * @param firstBuffer * the first buffer to compare against. Must have at least 20 - * bytes from position ai through the end of the buffer. + * bytes from position fi through the end of the buffer. * @param fi * first offset within firstBuffer to begin testing. * @param secondBuffer - * the second buffer to compare against. Must have at least 2 - * bytes from position bi through the end of the buffer. + * the second buffer to compare against. Must have at least 20 + * bytes from position si through the end of the buffer. * @param si * first offset within secondBuffer to begin testing. * @return true if the two identifiers are the same. @@ -230,9 +228,9 @@ public class ObjectId extends AnyObjectId implements Serializable { * @return the converted object id. */ public static ObjectId fromString(final String str) { - if (str.length() != Constants.OBJECT_ID_STRING_LENGTH) - throw new IllegalArgumentException( - MessageFormat.format(JGitText.get().invalidId, str)); + if (str.length() != Constants.OBJECT_ID_STRING_LENGTH) { + throw new InvalidObjectIdException(str); + } return fromHexString(Constants.encodeASCII(str), 0); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java index e8591195cf..2ecc60c3b6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java @@ -63,6 +63,54 @@ import org.eclipse.jgit.util.SystemReader; public class PersonIdent implements Serializable { private static final long serialVersionUID = 1L; + /** + * @param tzOffset + * timezone offset as in {@link #getTimeZoneOffset()}. + * @return time zone object for the given offset. + * @since 4.1 + */ + public static TimeZone getTimeZone(int tzOffset) { + StringBuilder tzId = new StringBuilder(8); + tzId.append("GMT"); //$NON-NLS-1$ + appendTimezone(tzId, tzOffset); + return TimeZone.getTimeZone(tzId.toString()); + } + + /** + * Format a timezone offset. + * + * @param r + * string builder to append to. + * @param offset + * timezone offset as in {@link #getTimeZoneOffset()}. + * @since 4.1 + */ + public static void appendTimezone(StringBuilder r, int offset) { + final char sign; + final int offsetHours; + final int offsetMins; + + if (offset < 0) { + sign = '-'; + offset = -offset; + } else { + sign = '+'; + } + + offsetHours = offset / 60; + offsetMins = offset % 60; + + r.append(sign); + if (offsetHours < 10) { + r.append('0'); + } + r.append(offsetHours); + if (offsetMins < 10) { + r.append('0'); + } + r.append(offsetMins); + } + private final String name; private final String emailAddress; @@ -217,10 +265,7 @@ public class PersonIdent implements Serializable { * @return this person's declared time zone; null if time zone is unknown. */ public TimeZone getTimeZone() { - StringBuilder tzId = new StringBuilder(8); - tzId.append("GMT"); //$NON-NLS-1$ - appendTimezone(tzId); - return TimeZone.getTimeZone(tzId.toString()); + return getTimeZone(tzOffset); } /** @@ -261,37 +306,10 @@ public class PersonIdent implements Serializable { r.append("> "); //$NON-NLS-1$ r.append(when / 1000); r.append(' '); - appendTimezone(r); + appendTimezone(r, tzOffset); return r.toString(); } - private void appendTimezone(final StringBuilder r) { - int offset = tzOffset; - final char sign; - final int offsetHours; - final int offsetMins; - - if (offset < 0) { - sign = '-'; - offset = -offset; - } else { - sign = '+'; - } - - offsetHours = offset / 60; - offsetMins = offset % 60; - - r.append(sign); - if (offsetHours < 10) { - r.append('0'); - } - r.append(offsetHours); - if (offsetMins < 10) { - r.append('0'); - } - r.append(offsetMins); - } - @SuppressWarnings("nls") public String toString() { final StringBuilder r = new StringBuilder(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index 7fea880612..b62033cbd2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -211,6 +212,9 @@ public abstract class RefDatabase { * Aside from taking advantage of {@link #SEARCH_PATH}, this method may be * able to more quickly resolve a single reference name than obtaining the * complete namespace by {@code getRefs(ALL).get(name)}. + * <p> + * To read a specific reference without using @{link #SEARCH_PATH}, see + * {@link #exactRef(String)}. * * @param name * the name of the reference. May be a short name which must be @@ -222,6 +226,76 @@ public abstract class RefDatabase { public abstract Ref getRef(String name) throws IOException; /** + * Read a single reference. + * <p> + * Unlike {@link #getRef}, this method expects an unshortened reference + * name and does not search using the standard {@link #SEARCH_PATH}. + * + * @param name + * the unabbreviated name of the reference. + * @return the reference (if it exists); else {@code null}. + * @throws IOException + * the reference space cannot be accessed. + * @since 4.1 + */ + public Ref exactRef(String name) throws IOException { + Ref ref = getRef(name); + if (ref == null || !name.equals(ref.getName())) { + return null; + } + return ref; + } + + /** + * Read the specified references. + * <p> + * This method expects a list of unshortened reference names and returns + * a map from reference names to refs. Any named references that do not + * exist will not be included in the returned map. + * + * @param refs + * the unabbreviated names of references to look up. + * @return modifiable map describing any refs that exist among the ref + * ref names supplied. The map can be an unsorted map. + * @throws IOException + * the reference space cannot be accessed. + * @since 4.1 + */ + public Map<String, Ref> exactRef(String... refs) throws IOException { + Map<String, Ref> result = new HashMap<>(refs.length); + for (String name : refs) { + Ref ref = exactRef(name); + if (ref != null) { + result.put(name, ref); + } + } + return result; + } + + /** + * Find the first named reference. + * <p> + * This method expects a list of unshortened reference names and returns + * the first that exists. + * + * @param refs + * the unabbreviated names of references to look up. + * @return the first named reference that exists (if any); else {@code null}. + * @throws IOException + * the reference space cannot be accessed. + * @since 4.1 + */ + public Ref firstExactRef(String... refs) throws IOException { + for (String name : refs) { + Ref ref = exactRef(name); + if (ref != null) { + return ref; + } + } + return null; + } + + /** * Get a section of the reference namespace. * * @param prefix @@ -242,6 +316,7 @@ public abstract class RefDatabase { * The result list includes non-ref items such as MERGE_HEAD and * FETCH_RESULT cast to be refs. The names of these refs are not returned by * <code>getRefs(ALL)</code> but are accepted by {@link #getRef(String)} + * and {@link #exactRef(String)}. * * @return a list of additional refs * @throws IOException diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java index aeef9f0744..4316cd0a1f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java @@ -52,6 +52,7 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.PushCertificate; /** * Creates, updates or deletes any reference. @@ -165,6 +166,9 @@ public abstract class RefUpdate { /** Result of the update operation. */ private Result result = Result.NOT_ATTEMPTED; + /** Push certificate associated with this update. */ + private PushCertificate pushCert; + private final Ref ref; /** @@ -414,6 +418,31 @@ public abstract class RefUpdate { } /** + * Set a push certificate associated with this update. + * <p> + * This usually includes a command to update this ref, but is not required to. + * + * @param cert + * push certificate, may be null. + * @since 4.1 + */ + public void setPushCertificate(PushCertificate cert) { + pushCert = cert; + } + + /** + * Set the push certificate associated with this update. + * <p> + * This usually includes a command to update this ref, but is not required to. + * + * @return push certificate, may be null. + * @since 4.1 + */ + protected PushCertificate getPushCertificate() { + return pushCert; + } + + /** * Get the status of this update. * <p> * The same value that was previously returned from an update method. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index fc7dca2d43..d4c72cb9cb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -849,8 +849,9 @@ public abstract class Repository implements AutoCloseable { * Except when HEAD is detached, in which case this method returns the * current ObjectId in hexadecimal string format. * - * @return name of current branch (for example {@code refs/heads/master}) or - * an ObjectId in hex format if the current branch is detached. + * @return name of current branch (for example {@code refs/heads/master}), + * an ObjectId in hex format if the current branch is detached, + * or null if the repository is corrupt and has no HEAD reference. * @throws IOException */ public String getFullBranch() throws IOException { @@ -871,8 +872,9 @@ public abstract class Repository implements AutoCloseable { * leading prefix {@code refs/heads/} is removed from the reference before * it is returned to the caller. * - * @return name of current branch (for example {@code master}), or an - * ObjectId in hex format if the current branch is detached. + * @return name of current branch (for example {@code master}), an + * ObjectId in hex format if the current branch is detached, + * or null if the repository is corrupt and has no HEAD reference. * @throws IOException */ public String getBranch() throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java index 0c58a0bea4..23cc264c1c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java @@ -47,6 +47,8 @@ import java.io.File; import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -143,6 +145,28 @@ public class RepositoryCache { } } + /** + * Remove a repository from the cache. + * <p> + * Removes a repository from the cache, if it is still registered here, + * permitting it to close. + * + * @param location + * location of the repository to remove. + * @since 4.1 + */ + public static void unregister(Key location) { + cache.unregisterRepository(location); + } + + /** + * @return the locations of all repositories registered in the cache. + * @since 4.1 + */ + public static Collection<Key> getRegisteredKeys() { + return cache.getKeys(); + } + /** Unregister all repositories from the cache. */ public static void clear() { cache.clearAll(); @@ -195,6 +219,10 @@ public class RepositoryCache { oldDb.close(); } + private Collection<Key> getKeys() { + return new ArrayList<Key>(cacheMap.keySet()); + } + private void clearAll() { for (int stage = 0; stage < 2; stage++) { for (Iterator<Map.Entry<Key, Reference<Repository>>> i = cacheMap 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 3654ffd1e8..8a6343c3cc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -525,10 +525,11 @@ public class ResolveMerger extends ThreeWayMerger { } } - if (nonTree(modeO) && modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) { + if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) { // THEIRS was not changed compared to BASE. All changes must be in // OURS. OURS is chosen. We can keep the existing entry. - keep(ourDce); + if (ourDce != null) + keep(ourDce); // no checkout needed! return true; } @@ -549,11 +550,12 @@ public class ResolveMerger extends ThreeWayMerger { if (e != null) toBeCheckedOut.put(tw.getPathString(), e); return true; - } else if (modeT == 0 && modeB != 0) { - // we want THEIRS ... but THEIRS contains the deletion of the - // file. Also, do not complain if the file is already deleted - // locally. This complements the test in isWorktreeDirty() for - // the same case. + } else { + // we want THEIRS ... but THEIRS contains a folder or the + // deletion of the path. Delete what's in the workingtree (the + // workingtree is clean) but do not complain if the file is + // already deleted locally. This complements the test in + // isWorktreeDirty() for the same case. if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) return true; toBeDeleted.add(tw.getPathString()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java index ea904cd1db..79fbb09ecc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java @@ -260,8 +260,8 @@ class FanoutBucket extends InMemoryNoteBucket { } ObjectId getTreeId() { - try { - return new ObjectInserter.Formatter().idFor(build(false, null)); + try (ObjectInserter.Formatter f = new ObjectInserter.Formatter()) { + return f.idFor(build(false, null)); } catch (IOException e) { // should never happen as we are not inserting throw new RuntimeException(e); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java index afb208ecf9..1176d958b0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -1406,6 +1406,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable { /** * Assume additional commits are shallow (have no parents). + * <p> + * This method is a No-op if the collection is empty. * * @param ids * commits that should be treated as shallow commits, in addition diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java new file mode 100644 index 0000000000..a811fe3cde --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2015, Google Inc. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.storage.pack; + +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 java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.pack.CachedPack; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Statistics about {@link org.eclipse.jgit.internal.storage.pack.PackWriter} + * pack creation. + * + * @since 4.1 + */ +public class PackStatistics { + /** + * Statistics about a single type of object (commits, tags, trees and + * blobs). + */ + public static class ObjectType { + /** + * POJO for accumulating the ObjectType statistics. + */ + public static class Accumulator { + /** Count of objects of this type. */ + public long cntObjects; + + /** Count of deltas of this type. */ + public long cntDeltas; + + /** Count of reused objects of this type. */ + public long reusedObjects; + + /** Count of reused deltas of this type. */ + public long reusedDeltas; + + /** Count of bytes for all objects of this type. */ + public long bytes; + + /** Count of delta bytes for objects of this type. */ + public long deltaBytes; + } + + private ObjectType.Accumulator objectType; + + /** + * Creates a new {@link ObjectType} object from the accumulator. + * + * @param accumulator + * the accumulator of the statistics + */ + public ObjectType(ObjectType.Accumulator accumulator) { + /* + * For efficiency this wraps and serves up the Accumulator object + * rather than making a deep clone. Normal usage of PackWriter is to + * create a single pack/index/bitmap and only call getStatistics() + * after all work is complete. + */ + objectType = accumulator; + } + + /** + * @return total number of objects output. This total includes the value + * of {@link #getDeltas()}. + */ + public long getObjects() { + return objectType.cntObjects; + } + + /** + * @return total number of deltas output. This may be lower than the + * actual number of deltas if a cached pack was reused. + */ + public long getDeltas() { + return objectType.cntDeltas; + } + + /** + * @return number of objects whose existing representation was reused in + * the output. This count includes {@link #getReusedDeltas()}. + */ + public long getReusedObjects() { + return objectType.reusedObjects; + } + + /** + * @return number of deltas whose existing representation was reused in + * the output, as their base object was also output or was + * assumed present for a thin pack. This may be lower than the + * actual number of reused deltas if a cached pack was reused. + */ + public long getReusedDeltas() { + return objectType.reusedDeltas; + } + + /** + * @return total number of bytes written. This size includes the object + * headers as well as the compressed data. This size also + * includes all of {@link #getDeltaBytes()}. + */ + public long getBytes() { + return objectType.bytes; + } + + /** + * @return number of delta bytes written. This size includes the object + * headers for the delta objects. + */ + public long getDeltaBytes() { + return objectType.deltaBytes; + } + } + + /** + * POJO for accumulating the statistics. + */ + public static class Accumulator { + /** The set of objects to be included in the pack. */ + public Set<ObjectId> interestingObjects; + + /** The set of objects to be excluded from the pack. */ + public Set<ObjectId> uninterestingObjects; + + /** The set of shallow commits on the client. */ + public Set<ObjectId> clientShallowCommits; + + /** The collection of reused packs in the upload. */ + public List<CachedPack> reusedPacks; + + /** Commits with no parents. */ + public Set<ObjectId> rootCommits; + + /** If a shallow pack, the depth in commits. */ + public int depth; + + /** + * The count of objects in the pack that went through the delta search + * process in order to find a potential delta base. + */ + public int deltaSearchNonEdgeObjects; + + /** + * The count of objects in the pack that went through delta base search + * and found a suitable base. This is a subset of + * deltaSearchNonEdgeObjects. + */ + public int deltasFound; + + /** The total count of objects in the pack. */ + public long totalObjects; + + /** + * The count of objects that needed to be discovered through an object + * walk because they were not found in bitmap indices. + */ + public long bitmapIndexMisses; + + /** The total count of deltas output. */ + public long totalDeltas; + + /** The count of reused objects in the pack. */ + public long reusedObjects; + + /** The count of reused deltas in the pack. */ + public long reusedDeltas; + + /** The count of total bytes in the pack. */ + public long totalBytes; + + /** The size of the thin pack in bytes, if a thin pack was generated. */ + public long thinPackBytes; + + /** Time in ms spent counting the objects that will go into the pack. */ + public long timeCounting; + + /** Time in ms spent searching for objects to reuse. */ + public long timeSearchingForReuse; + + /** Time in ms spent searching for sizes of objects. */ + public long timeSearchingForSizes; + + /** Time in ms spent compressing the pack. */ + public long timeCompressing; + + /** Time in ms spent writing the pack. */ + public long timeWriting; + + /** + * Statistics about each object type in the pack (commits, tags, trees + * and blobs.) + */ + public ObjectType.Accumulator[] objectTypes; + + { + objectTypes = new ObjectType.Accumulator[5]; + objectTypes[OBJ_COMMIT] = new ObjectType.Accumulator(); + objectTypes[OBJ_TREE] = new ObjectType.Accumulator(); + objectTypes[OBJ_BLOB] = new ObjectType.Accumulator(); + objectTypes[OBJ_TAG] = new ObjectType.Accumulator(); + } + } + + private Accumulator statistics; + + /** + * Creates a new {@link PackStatistics} object from the accumulator. + * + * @param accumulator + * the accumulator of the statistics + */ + public PackStatistics(Accumulator accumulator) { + /* + * For efficiency this wraps and serves up the Accumulator object rather + * than making a deep clone. Normal usage of PackWriter is to create a + * single pack/index/bitmap and only call getStatistics() after all work + * is complete. + */ + statistics = accumulator; + } + + /** + * @return unmodifiable collection of objects to be included in the pack. + * May be {@code null} if the pack was hand-crafted in a unit test. + */ + public Set<ObjectId> getInterestingObjects() { + return statistics.interestingObjects; + } + + /** + * @return unmodifiable collection of objects that should be excluded from + * the pack, as the peer that will receive the pack already has + * these objects. + */ + public Set<ObjectId> getUninterestingObjects() { + return statistics.uninterestingObjects; + } + + /** + * @return unmodifiable collection of objects that were shallow commits on + * the client. + */ + public Set<ObjectId> getClientShallowCommits() { + return statistics.clientShallowCommits; + } + + /** + * @return unmodifiable list of the cached packs that were reused in the + * output, if any were selected for reuse. + */ + public List<CachedPack> getReusedPacks() { + return statistics.reusedPacks; + } + + /** @return unmodifiable collection of the root commits of the history. */ + public Set<ObjectId> getRootCommits() { + return statistics.rootCommits; + } + + /** + * @return number of objects in the output pack that went through the delta + * search process in order to find a potential delta base. + */ + public int getDeltaSearchNonEdgeObjects() { + return statistics.deltaSearchNonEdgeObjects; + } + + /** + * @return number of objects in the output pack that went through delta base + * search and found a suitable base. This is a subset of + * {@link #getDeltaSearchNonEdgeObjects()}. + */ + public int getDeltasFound() { + return statistics.deltasFound; + } + + /** + * @return total number of objects output. This total includes the value of + * {@link #getTotalDeltas()}. + */ + public long getTotalObjects() { + return statistics.totalObjects; + } + + /** + * @return the count of objects that needed to be discovered through an + * object walk because they were not found in bitmap indices. + * Returns -1 if no bitmap indices were found. + */ + public long getBitmapIndexMisses() { + return statistics.bitmapIndexMisses; + } + + /** + * @return total number of deltas output. This may be lower than the actual + * number of deltas if a cached pack was reused. + */ + public long getTotalDeltas() { + return statistics.totalDeltas; + } + + /** + * @return number of objects whose existing representation was reused in the + * output. This count includes {@link #getReusedDeltas()}. + */ + public long getReusedObjects() { + return statistics.reusedObjects; + } + + /** + * @return number of deltas whose existing representation was reused in the + * output, as their base object was also output or was assumed + * present for a thin pack. This may be lower than the actual number + * of reused deltas if a cached pack was reused. + */ + public long getReusedDeltas() { + return statistics.reusedDeltas; + } + + /** + * @return total number of bytes written. This size includes the pack + * header, trailer, thin pack, and reused cached pack(s). + */ + public long getTotalBytes() { + return statistics.totalBytes; + } + + /** + * @return size of the thin pack in bytes, if a thin pack was generated. A + * thin pack is created when the client already has objects and some + * deltas are created against those objects, or if a cached pack is + * being used and some deltas will reference objects in the cached + * pack. This size does not include the pack header or trailer. + */ + public long getThinPackBytes() { + return statistics.thinPackBytes; + } + + /** + * @param typeCode + * object type code, e.g. OBJ_COMMIT or OBJ_TREE. + * @return information about this type of object in the pack. + */ + public ObjectType byObjectType(int typeCode) { + return new ObjectType(statistics.objectTypes[typeCode]); + } + + /** @return true if the resulting pack file was a shallow pack. */ + public boolean isShallow() { + return statistics.depth > 0; + } + + /** @return depth (in commits) the pack includes if shallow. */ + public int getDepth() { + return statistics.depth; + } + + /** + * @return time in milliseconds spent enumerating the objects that need to + * be included in the output. This time includes any restarts that + * occur when a cached pack is selected for reuse. + */ + public long getTimeCounting() { + return statistics.timeCounting; + } + + /** + * @return time in milliseconds spent matching existing representations + * against objects that will be transmitted, or that the client can + * be assumed to already have. + */ + public long getTimeSearchingForReuse() { + return statistics.timeSearchingForReuse; + } + + /** + * @return time in milliseconds spent finding the sizes of all objects that + * will enter the delta compression search window. The sizes need to + * be known to better match similar objects together and improve + * delta compression ratios. + */ + public long getTimeSearchingForSizes() { + return statistics.timeSearchingForSizes; + } + + /** + * @return time in milliseconds spent on delta compression. This is observed + * wall-clock time and does not accurately track CPU time used when + * multiple threads were used to perform the delta compression. + */ + public long getTimeCompressing() { + return statistics.timeCompressing; + } + + /** + * @return time in milliseconds spent writing the pack output, from start of + * header until end of trailer. The transfer speed can be + * approximated by dividing {@link #getTotalBytes()} by this value. + */ + public long getTimeWriting() { + return statistics.timeWriting; + } + + /** @return total time spent processing this pack. */ + public long getTimeTotal() { + return statistics.timeCounting + statistics.timeSearchingForReuse + + statistics.timeSearchingForSizes + statistics.timeCompressing + + statistics.timeWriting; + } + + /** + * @return get the average output speed in terms of bytes-per-second. + * {@code getTotalBytes() / (getTimeWriting() / 1000.0)}. + */ + public double getTransferRate() { + return getTotalBytes() / (getTimeWriting() / 1000.0); + } + + /** @return formatted message string for display to clients. */ + public String getMessage() { + return MessageFormat.format(JGitText.get().packWriterStatistics, + Long.valueOf(statistics.totalObjects), + Long.valueOf(statistics.totalDeltas), + Long.valueOf(statistics.reusedObjects), + Long.valueOf(statistics.reusedDeltas)); + } + + /** @return a map containing ObjectType statistics. */ + public Map<Integer, ObjectType> getObjectTypes() { + HashMap<Integer, ObjectType> map = new HashMap<>(); + map.put(Integer.valueOf(OBJ_BLOB), new ObjectType( + statistics.objectTypes[OBJ_BLOB])); + map.put(Integer.valueOf(OBJ_COMMIT), new ObjectType( + statistics.objectTypes[OBJ_COMMIT])); + map.put(Integer.valueOf(OBJ_TAG), new ObjectType( + statistics.objectTypes[OBJ_TAG])); + map.put(Integer.valueOf(OBJ_TREE), new ObjectType( + statistics.objectTypes[OBJ_TREE])); + return map; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java index 7d9bca0d1b..6263d4b74d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java @@ -451,13 +451,14 @@ public class SubmoduleWalk implements AutoCloseable { } /** - * Checks whether the working tree (or the index in case of a bare repo) - * contains a .gitmodules file. That's a hint that the repo contains - * submodules. + * Checks whether the working tree contains a .gitmodules file. That's a + * hint that the repo contains submodules. * * @param repository * the repository to check - * @return <code>true</code> if the repo contains a .gitmodules file + * @return <code>true</code> if the working tree contains a .gitmodules file, + * <code>false</code> otherwise. Always returns <code>false</code> + * for bare repositories. * @throws IOException * @throws CorruptObjectException * @since 3.6 @@ -465,8 +466,7 @@ public class SubmoduleWalk implements AutoCloseable { public static boolean containsGitModulesFile(Repository repository) throws IOException { if (repository.isBare()) { - DirCache dc = repository.readDirCache(); - return (dc.findEntry(Constants.DOT_GIT_MODULES) >= 0); + return false; } File modulesFile = new File(repository.getWorkTree(), Constants.DOT_GIT_MODULES); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java index a6fc633593..cf13582db5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -193,6 +193,13 @@ public abstract class BasePackFetchConnection extends BasePackConnection */ public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT; + /** + * The client supports fetching objects that are reachable from a tip of a + * ref that is allowed to fetch. + * @since 4.1 + */ + public static final String OPTION_ALLOW_REACHABLE_SHA1_IN_WANT = GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT; + private final RevWalk walk; /** All commits that are immediately reachable by a local ref. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index 9112ecbe34..776a9f695a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -252,13 +252,35 @@ public abstract class BaseReceivePack { /** The size of the received pack, including index size */ private Long packSize; - PushCertificateParser pushCertificateParser; + private PushCertificateParser pushCertificateParser; + private SignedPushConfig signedPushConfig; + private PushCertificate pushCert; /** - * @return the push certificate used to verify the pushers identity. + * Get the push certificate used to verify the pusher's identity. + * <p> + * Only valid after commands are read from the wire. + * + * @return the parsed certificate, or null if push certificates are disabled + * or no cert was presented by the client. + * @since 4.1 */ - PushCertificate getPushCertificate() { - return pushCertificateParser; + public PushCertificate getPushCertificate() { + return pushCert; + } + + /** + * Set the push certificate used to verify the pusher's identity. + * <p> + * Should only be called if reconstructing an instance without going through + * the normal {@link #recvCommands()} flow. + * + * @param cert + * the push certificate to set. + * @since 4.1 + */ + public void setPushCertificate(PushCertificate cert) { + pushCert = cert; } /** @@ -282,7 +304,7 @@ public abstract class BaseReceivePack { refFilter = RefFilter.DEFAULT; advertisedHaves = new HashSet<ObjectId>(); clientShallowCommits = new HashSet<ObjectId>(); - pushCertificateParser = new PushCertificateParser(db, cfg); + signedPushConfig = cfg.signedPush; } /** Configuration for receive operations. */ @@ -304,8 +326,7 @@ public abstract class BaseReceivePack { final boolean allowNonFastForwards; final boolean allowOfsDelta; - final String certNonceSeed; - final int certNonceSlopLimit; + final SignedPushConfig signedPush; ReceiveConfig(final Config config) { checkReceivedObjects = config.getBoolean( @@ -326,8 +347,7 @@ public abstract class BaseReceivePack { "denynonfastforwards", false); //$NON-NLS-1$ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ true); - certNonceSeed = config.getString("receive", null, "certnonceseed"); //$NON-NLS-1$ //$NON-NLS-2$ - certNonceSlopLimit = config.getInt("receive", "certnonceslop", 0); //$NON-NLS-1$ //$NON-NLS-2$ + signedPush = SignedPushConfig.KEY.parse(config); } ObjectChecker newObjectChecker() { @@ -784,6 +804,26 @@ public abstract class BaseReceivePack { } /** + * Set the configuration for push certificate verification. + * + * @param cfg + * new configuration; if this object is null or its {@link + * SignedPushConfig#getCertNonceSeed()} is null, push certificate + * verification will be disabled. + * @since 4.1 + */ + public void setSignedPushConfig(SignedPushConfig cfg) { + signedPushConfig = cfg; + } + + private PushCertificateParser getPushCertificateParser() { + if (pushCertificateParser == null) { + pushCertificateParser = new PushCertificateParser(db, signedPushConfig); + } + return pushCertificateParser; + } + + /** * Get the user agent of the client. * <p> * If the client is new enough to use {@code agent=} capability that value @@ -1014,9 +1054,10 @@ public abstract class BaseReceivePack { adv.advertiseCapability(CAPABILITY_REPORT_STATUS); if (allowQuiet) adv.advertiseCapability(CAPABILITY_QUIET); - if (pushCertificateParser.enabled()) - adv.advertiseCapability( - pushCertificateParser.getAdvertiseNonce()); + String nonce = getPushCertificateParser().getAdvertiseNonce(); + if (nonce != null) { + adv.advertiseCapability(nonce); + } if (db.getRefDatabase().performsAtomicTransactions()) adv.advertiseCapability(CAPABILITY_ATOMIC); if (allowOfsDelta) @@ -1036,56 +1077,88 @@ public abstract class BaseReceivePack { * @throws IOException */ protected void recvCommands() throws IOException { - for (;;) { - String line; - try { - line = pckIn.readStringRaw(); - } catch (EOFException eof) { - if (commands.isEmpty()) - return; - throw eof; - } - if (line == PacketLineIn.END) - break; - - if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$ - clientShallowCommits.add(ObjectId.fromString(line.substring(8, 48))); - continue; - } + PushCertificateParser certParser = getPushCertificateParser(); + FirstLine firstLine = null; + try { + for (;;) { + String line; + try { + line = pckIn.readString(); + } catch (EOFException eof) { + if (commands.isEmpty()) + return; + throw eof; + } + if (line == PacketLineIn.END) { + break; + } - if (commands.isEmpty()) { - final FirstLine firstLine = new FirstLine(line); - enabledCapabilities = firstLine.getCapabilities(); - line = firstLine.getLine(); + if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$ + clientShallowCommits.add(ObjectId.fromString(line.substring(8, 48))); + continue; + } - if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) - pushCertificateParser.receiveHeader(pckIn, - !isBiDirectionalPipe()); - } + if (firstLine == null) { + firstLine = new FirstLine(line); + enabledCapabilities = firstLine.getCapabilities(); + line = firstLine.getLine(); - if (line.equals("-----BEGIN PGP SIGNATURE-----\n")) //$NON-NLS-1$ - pushCertificateParser.receiveSignature(pckIn); + if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) { + certParser.receiveHeader(pckIn, !isBiDirectionalPipe()); + continue; + } + } - if (pushCertificateParser.enabled()) - pushCertificateParser.addCommand(line); + if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) { + certParser.receiveSignature(pckIn); + continue; + } - if (line.length() < 83) { - final String m = JGitText.get().errorInvalidProtocolWantedOldNewRef; - sendError(m); - throw new PackProtocolException(m); + ReceiveCommand cmd; + try { + cmd = parseCommand(line); + } catch (PackProtocolException e) { + sendError(e.getMessage()); + throw e; + } + if (cmd.getRefName().equals(Constants.HEAD)) { + cmd.setResult(Result.REJECTED_CURRENT_BRANCH); + } else { + cmd.setRef(refs.get(cmd.getRefName())); + } + commands.add(cmd); + if (certParser.enabled()) { + certParser.addCommand(cmd); + } } + pushCert = certParser.build(); + } catch (PackProtocolException e) { + sendError(e.getMessage()); + throw e; + } + } - final ObjectId oldId = ObjectId.fromString(line.substring(0, 40)); - final ObjectId newId = ObjectId.fromString(line.substring(41, 81)); - final String name = line.substring(82); - final ReceiveCommand cmd = new ReceiveCommand(oldId, newId, name); - if (name.equals(Constants.HEAD)) { - cmd.setResult(Result.REJECTED_CURRENT_BRANCH); - } else { - cmd.setRef(refs.get(cmd.getRefName())); - } - commands.add(cmd); + static ReceiveCommand parseCommand(String line) throws PackProtocolException { + if (line == null || line.length() < 83) { + throw new PackProtocolException( + JGitText.get().errorInvalidProtocolWantedOldNewRef); + } + String oldStr = line.substring(0, 40); + String newStr = line.substring(41, 81); + ObjectId oldId, newId; + try { + oldId = ObjectId.fromString(oldStr); + newId = ObjectId.fromString(newStr); + } catch (IllegalArgumentException e) { + throw new PackProtocolException( + JGitText.get().errorInvalidProtocolWantedOldNewRef, e); + } + String name = line.substring(82); + if (!Repository.isValidRefName(name)) { + throw new PackProtocolException( + JGitText.get().errorInvalidProtocolWantedOldNewRef); } + return new ReceiveCommand(oldId, newId, name); } /** Enable capabilities based on a previously read capabilities line. */ @@ -1432,6 +1505,7 @@ public abstract class BaseReceivePack { batch.setRefLogMessage("push", true); //$NON-NLS-1$ batch.addCommand(toApply); try { + batch.setPushCertificate(getPushCertificate()); batch.execute(walk, updating); } catch (IOException err) { for (ReceiveCommand cmd : toApply) { 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 81ad981918..ca624c03d5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -92,6 +92,8 @@ public class BundleWriter { private PackConfig packConfig; + private ObjectCountCallback callback; + /** * Create a writer for a bundle. * @@ -188,6 +190,9 @@ public class BundleWriter { * an error occurred reading a local object's data to include in * the bundle, or writing compressed object data to the output * stream. + * @throws WriteAbortedException + * the write operation is aborted by + * {@link ObjectCountCallback}. */ public void writeBundle(ProgressMonitor monitor, OutputStream os) throws IOException { @@ -195,6 +200,8 @@ public class BundleWriter { if (pc == null) pc = new PackConfig(db); try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) { + packWriter.setObjectCountCallback(callback); + final HashSet<ObjectId> inc = new HashSet<ObjectId>(); final HashSet<ObjectId> exc = new HashSet<ObjectId>(); inc.addAll(include.values()); @@ -234,4 +241,24 @@ public class BundleWriter { packWriter.writePack(monitor, monitor, os); } } + + /** + * Set the {@link ObjectCountCallback}. + * <p> + * It should be set before calling + * {@link #writeBundle(ProgressMonitor, OutputStream)}. + * <p> + * This callback will be passed on to + * {@link PackWriter#setObjectCountCallback}. + * + * @param callback + * the callback to set + * + * @return this object for chaining. + * @since 4.1 + */ + public BundleWriter setObjectCountCallback(ObjectCountCallback callback) { + this.callback = callback; + return this; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java index 8d2d554b91..efde062621 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java @@ -130,6 +130,14 @@ public class GitProtocolConstants { public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = "allow-tip-sha1-in-want"; //$NON-NLS-1$ /** + * The client supports fetching objects that are reachable from a tip of a + * ref that is allowed to fetch. + * + * @since 4.1 + */ + public static final String OPTION_ALLOW_REACHABLE_SHA1_IN_WANT = "allow-reachable-sha1-in-want"; //$NON-NLS-1$ + + /** * Symbolic reference support for better negotiation. * * @since 3.6 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java index 222ca55d5c..7e9434a0f0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java @@ -105,36 +105,42 @@ public class HMACSHA1NonceGenerator implements NonceGenerator { @Override public NonceStatus verify(String received, String sent, Repository db, boolean allowSlop, int slop) { - if (received.isEmpty()) + if (received.isEmpty()) { return NonceStatus.MISSING; - else if (sent.isEmpty()) + } else if (sent.isEmpty()) { return NonceStatus.UNSOLICITED; - else if (received.equals(sent)) + } else if (received.equals(sent)) { return NonceStatus.OK; + } - if (!allowSlop) + if (!allowSlop) { return NonceStatus.BAD; + } /* nonce is concat(<seconds-since-epoch>, "-", <hmac>) */ int idxSent = sent.indexOf('-'); int idxRecv = received.indexOf('-'); - if (idxSent == -1 || idxRecv == -1) + if (idxSent == -1 || idxRecv == -1) { return NonceStatus.BAD; + } + String signedStampStr = received.substring(0, idxRecv); + String advertisedStampStr = sent.substring(0, idxSent); long signedStamp; long advertisedStamp; try { - signedStamp = Long.parseLong(received.substring(0, idxRecv)); - advertisedStamp = Long.parseLong(sent.substring(0, idxSent)); - } catch (Exception e) { + signedStamp = Long.parseLong(signedStampStr); + advertisedStamp = Long.parseLong(advertisedStampStr); + } catch (IllegalArgumentException e) { return NonceStatus.BAD; } // what we would have signed earlier String expect = createNonce(db, signedStamp); - if (!expect.equals(received)) + if (!expect.equals(received)) { return NonceStatus.BAD; + } long nonceStampSlop = Math.abs(advertisedStamp - signedStamp); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectCountCallback.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectCountCallback.java new file mode 100644 index 0000000000..cf81adbcd0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectCountCallback.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import java.io.OutputStream; + +import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.lib.ProgressMonitor; + +/** + * A callback to tell caller the count of objects ASAP. + * + * @since 4.1 + */ +public interface ObjectCountCallback { + /** + * Invoked when the {@link PackWriter} has counted the objects to be + * written to pack. + * <p> + * An {@code ObjectCountCallback} can use this information to decide + * whether the + * {@link PackWriter#writePack(ProgressMonitor, ProgressMonitor, OutputStream)} + * operation should be aborted. + * <p> + * This callback will be called exactly once. + * + * @param objectCount + * the count of the objects. + * @throws WriteAbortedException + * to indicate that the write operation should be aborted. + */ + void setObjectCount(long objectCount) throws WriteAbortedException; +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java index 04abe22323..918df94de9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -135,6 +135,8 @@ public abstract class PackParser { private boolean allowThin; + private boolean checkObjectCollisions; + private boolean needBaseObjectIds; private boolean checkEofAfterPackFooter; @@ -204,6 +206,7 @@ public abstract class PackParser { objectDigest = Constants.newMessageDigest(); tempObjectId = new MutableObjectId(); packDigest = Constants.newMessageDigest(); + checkObjectCollisions = true; } /** @return true if a thin pack (missing base objects) is permitted. */ @@ -225,6 +228,39 @@ public abstract class PackParser { } /** + * @return if true received objects are verified to prevent collisions. + * @since 4.1 + */ + protected boolean isCheckObjectCollisions() { + return checkObjectCollisions; + } + + /** + * Enable checking for collisions with existing objects. + * <p> + * By default PackParser looks for each received object in the repository. + * If the object already exists, the existing object is compared + * byte-for-byte with the newly received copy to ensure they are identical. + * The receive is aborted with an exception if any byte differs. This check + * is necessary to prevent an evil attacker from supplying a replacement + * object into this repository in the event that a discovery enabling SHA-1 + * collisions is made. + * <p> + * This check may be very costly to perform, and some repositories may have + * other ways to segregate newly received object data. The check is enabled + * by default, but can be explicitly disabled if the implementation can + * provide the same guarantee, or is willing to accept the risks associated + * with bypassing the check. + * + * @param check + * true to enable collision checking (strongly encouraged). + * @since 4.1 + */ + protected void setCheckObjectCollisions(boolean check) { + checkObjectCollisions = check; + } + + /** * Configure this index pack instance to keep track of new objects. * <p> * By default an index pack doesn't save the new objects that were created @@ -988,7 +1024,8 @@ public abstract class PackParser { } inf.close(); tempObjectId.fromRaw(objectDigest.digest(), 0); - checkContentLater = readCurs.has(tempObjectId); + checkContentLater = isCheckObjectCollisions() + && readCurs.has(tempObjectId); data = null; } else { @@ -1022,17 +1059,19 @@ public abstract class PackParser { } } - try { - final ObjectLoader ldr = readCurs.open(id, type); - final byte[] existingData = ldr.getCachedBytes(data.length); - if (!Arrays.equals(data, existingData)) { - throw new IOException(MessageFormat.format( - JGitText.get().collisionOn, id.name())); + if (isCheckObjectCollisions()) { + try { + final ObjectLoader ldr = readCurs.open(id, type); + final byte[] existingData = ldr.getCachedBytes(data.length); + if (!Arrays.equals(data, existingData)) { + throw new IOException(MessageFormat.format( + JGitText.get().collisionOn, id.name())); + } + } catch (MissingObjectException notLocal) { + // This is OK, we don't have a copy of the object locally + // but the API throws when we try to read it as usually its + // an error to read something that doesn't exist. } - } catch (MissingObjectException notLocal) { - // This is OK, we don't have a copy of the object locally - // but the API throws when we try to read it as usually its - // an error to read something that doesn't exist. } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java new file mode 100644 index 0000000000..53eeab1dff --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015, Google Inc. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.storage.pack.PackStatistics; + +/** + * Hook invoked by {@link UploadPack} after the pack has been uploaded. + * <p> + * Implementors of the interface are responsible for associating the current + * thread to a particular connection, if they need to also include connection + * information. One method is to use a {@link java.lang.ThreadLocal} to remember + * the connection information before invoking UploadPack. + * + * @since 4.1 + */ +public interface PostUploadHook { + /** A simple no-op hook. */ + public static final PostUploadHook NULL = new PostUploadHook() { + public void onPostUpload(PackStatistics stats) { + // Do nothing. + } + }; + + /** + * Notifies the hook that a pack has been sent. + * + * @param stats + * the statistics gathered by {@link PackWriter} for the uploaded + * pack + */ + public void onPostUpload(PackStatistics stats); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java new file mode 100644 index 0000000000..4e2eaeaff1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015, Google Inc. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import java.util.List; + +import org.eclipse.jgit.storage.pack.PackStatistics; + +/** + * {@link PostUploadHook} that delegates to a list of other hooks. + * <p> + * Hooks are run in the order passed to the constructor. + * + * @since 4.1 + */ +public class PostUploadHookChain implements PostUploadHook { + private final PostUploadHook[] hooks; + private final int count; + + /** + * Create a new hook chaining the given hooks together. + * + * @param hooks + * hooks to execute, in order. + * @return a new chain of the given hooks. + */ + public static PostUploadHook newChain(List<? extends PostUploadHook> hooks) { + PostUploadHook[] newHooks = new PostUploadHook[hooks.size()]; + int i = 0; + for (PostUploadHook hook : hooks) + if (hook != PostUploadHook.NULL) + newHooks[i++] = hook; + if (i == 0) + return PostUploadHook.NULL; + else if (i == 1) + return newHooks[0]; + else + return new PostUploadHookChain(newHooks, i); + } + + public void onPostUpload(PackStatistics stats) { + for (int i = 0; i < count; i++) + hooks[i].onPostUpload(stats); + } + + private PostUploadHookChain(PostUploadHook[] hooks, int count) { + this.hooks = hooks; + this.count = count; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java index 8ee4c17bf2..e450345b56 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java @@ -43,21 +43,26 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.PushCertificateParser.NONCE; +import static org.eclipse.jgit.transport.PushCertificateParser.PUSHEE; +import static org.eclipse.jgit.transport.PushCertificateParser.PUSHER; +import static org.eclipse.jgit.transport.PushCertificateParser.VERSION; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jgit.internal.JGitText; + /** * The required information to verify the push. + * <p> + * A valid certificate will not return null from any getter methods; callers may + * assume that any null value indicates a missing or invalid certificate. * * @since 4.0 */ public class PushCertificate { - /** The tuple "name <email>" as presented in the push certificate. */ - String pusher; - - /** The remote URL the signed push goes to. */ - String pushee; - - /** What we think about the returned signed nonce. */ - NonceStatus nonceStatus; - /** Verification result of the nonce returned during push. */ public enum NonceStatus { /** Nonce was not expected, yet client sent one anyway. */ @@ -66,47 +71,206 @@ public class PushCertificate { BAD, /** Nonce is required, but was not sent by client. */ MISSING, - /** Received nonce is valid. */ + /** + * Received nonce matches sent nonce, or is valid within the accepted slop + * window. + */ OK, - /** Received nonce is valid and within the accepted slop window. */ + /** Received nonce is valid, but outside the accepted slop window. */ SLOP } - String commandList; - String signature; + private final String version; + private final PushCertificateIdent pusher; + private final String pushee; + private final String nonce; + private final NonceStatus nonceStatus; + private final List<ReceiveCommand> commands; + private final String signature; + + PushCertificate(String version, PushCertificateIdent pusher, String pushee, + String nonce, NonceStatus nonceStatus, List<ReceiveCommand> commands, + String signature) { + if (version == null || version.isEmpty()) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().pushCertificateInvalidField, VERSION)); + } + if (pusher == null) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().pushCertificateInvalidField, PUSHER)); + } + if (nonce == null || nonce.isEmpty()) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().pushCertificateInvalidField, NONCE)); + } + if (nonceStatus == null) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().pushCertificateInvalidField, + "nonce status")); //$NON-NLS-1$ + } + if (commands == null || commands.isEmpty()) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().pushCertificateInvalidField, + "command")); //$NON-NLS-1$ + } + if (signature == null || signature.isEmpty()) { + throw new IllegalArgumentException( + JGitText.get().pushCertificateInvalidSignature); + } + if (!signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE) + || !signature.endsWith(PushCertificateParser.END_SIGNATURE + '\n')) { + throw new IllegalArgumentException( + JGitText.get().pushCertificateInvalidSignature); + } + this.version = version; + this.pusher = pusher; + this.pushee = pushee; + this.nonce = nonce; + this.nonceStatus = nonceStatus; + this.commands = commands; + this.signature = signature; + } /** - * @return the signature, consisting of the lines received between the lines - * '----BEGIN GPG SIGNATURE-----\n' and the '----END GPG - * SIGNATURE-----\n' + * @return the certificate version string. + * @since 4.1 */ - public String getSignature() { - return signature; + public String getVersion() { + return version; } /** - * @return the list of commands as one string to be feed into the signature - * verifier. + * @return the raw line that signed the cert, as a string. + * @since 4.0 */ - public String getCommandList() { - return commandList; + public String getPusher() { + return pusher.getRaw(); } /** - * @return the tuple "name <email>" as presented by the client in the - * push certificate. + * @return identity of the pusher who signed the cert. + * @since 4.1 */ - public String getPusher() { + public PushCertificateIdent getPusherIdent() { return pusher; } - /** @return URL of the repository the push was originally sent to. */ + /** + * @return URL of the repository the push was originally sent to. + * @since 4.0 + */ public String getPushee() { return pushee; } - /** @return verification status of the nonce embedded in the certificate. */ + /** + * @return the raw nonce value that was presented by the pusher. + * @since 4.1 + */ + public String getNonce() { + return nonce; + } + + /** + * @return verification status of the nonce embedded in the certificate. + * @since 4.0 + */ public NonceStatus getNonceStatus() { return nonceStatus; } + + /** + * @return the list of commands as one string to be feed into the signature + * verifier. + * @since 4.1 + */ + public List<ReceiveCommand> getCommands() { + return commands; + } + + /** + * @return the raw signature, consisting of the lines received between the + * lines {@code "----BEGIN GPG SIGNATURE-----\n"} and + * {@code "----END GPG SIGNATURE-----\n}", inclusive. + * @since 4.0 + */ + public String getSignature() { + return signature; + } + + /** + * @return text payload of the certificate for the signature verifier. + * @since 4.1 + */ + public String toText() { + return toStringBuilder().toString(); + } + + /** + * @return original text payload plus signature; the final output will be + * valid as input to {@link PushCertificateParser#fromString(String)}. + * @since 4.1 + */ + public String toTextWithSignature() { + return toStringBuilder().append(signature).toString(); + } + + private StringBuilder toStringBuilder() { + StringBuilder sb = new StringBuilder() + .append(VERSION).append(' ').append(version).append('\n') + .append(PUSHER).append(' ').append(getPusher()) + .append('\n'); + if (pushee != null) { + sb.append(PUSHEE).append(' ').append(pushee).append('\n'); + } + sb.append(NONCE).append(' ').append(nonce).append('\n') + .append('\n'); + for (ReceiveCommand cmd : commands) { + sb.append(cmd.getOldId().name()) + .append(' ').append(cmd.getNewId().name()) + .append(' ').append(cmd.getRefName()).append('\n'); + } + return sb; + } + + @Override + public int hashCode() { + return signature.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof PushCertificate)) { + return false; + } + PushCertificate p = (PushCertificate) o; + return version.equals(p.version) + && pusher.equals(p.pusher) + && Objects.equals(pushee, p.pushee) + && nonceStatus == p.nonceStatus + && signature.equals(p.signature) + && commandsEqual(this, p); + } + + private static boolean commandsEqual(PushCertificate c1, PushCertificate c2) { + if (c1.commands.size() != c2.commands.size()) { + return false; + } + for (int i = 0; i < c1.commands.size(); i++) { + ReceiveCommand cmd1 = c1.commands.get(i); + ReceiveCommand cmd2 = c2.commands.get(i); + if (!cmd1.getOldId().equals(cmd2.getOldId()) + || !cmd1.getNewId().equals(cmd2.getNewId()) + || !cmd1.getRefName().equals(cmd2.getRefName())) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return getClass().getSimpleName() + '[' + + toTextWithSignature() + ']'; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java new file mode 100644 index 0000000000..871a6f752b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import static org.eclipse.jgit.util.RawParseUtils.lastIndexOfTrim; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.util.MutableInteger; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Identity in a push certificate. + * <p> + * This is similar to a {@link PersonIdent} in that it contains a name, + * timestamp, and timezone offset, but differs in the following ways: + * <ul> + * <li>It is always parsed from a UTF-8 string, rather than a raw commit + * buffer.</li> + * <li>It is not guaranteed to contain a name and email portion, since any UTF-8 + * string is a valid OpenPGP User ID (RFC4880 5.1.1). The raw User ID is + * always available as {@link #getUserId()}, but {@link #getEmailAddress()} + * may return null.</li> + * <li>The raw text from which the identity was parsed is available with {@link + * #getRaw()}. This is necessary for losslessly reconstructing the signed push + * certificate payload.</li> + * <li> + * </ul> + * + * @since 4.1 + */ +public class PushCertificateIdent { + /** + * Parse an identity from a string. + * <p> + * Spaces are trimmed when parsing the timestamp and timezone offset, with one + * exception. The timestamp must be preceded by a single space, and the rest + * of the string prior to that space (including any additional whitespace) is + * treated as the OpenPGP User ID. + * <p> + * If either the timestamp or timezone offsets are missing, mimics {@link + * RawParseUtils#parsePersonIdent(String)} behavior and sets them both to + * zero. + * + * @param str + * string to parse. + * @return identity, never null. + */ + public static PushCertificateIdent parse(String str) { + MutableInteger p = new MutableInteger(); + byte[] raw = str.getBytes(UTF_8); + int tzBegin = raw.length - 1; + tzBegin = lastIndexOfTrim(raw, ' ', tzBegin); + if (tzBegin < 0 || raw[tzBegin] != ' ') { + return new PushCertificateIdent(str, str, 0, 0); + } + int whenBegin = tzBegin++; + int tz = RawParseUtils.parseTimeZoneOffset(raw, tzBegin, p); + boolean hasTz = p.value != tzBegin; + + whenBegin = lastIndexOfTrim(raw, ' ', whenBegin); + if (whenBegin < 0 || raw[whenBegin] != ' ') { + return new PushCertificateIdent(str, str, 0, 0); + } + int idEnd = whenBegin++; + long when = RawParseUtils.parseLongBase10(raw, whenBegin, p); + boolean hasWhen = p.value != whenBegin; + + if (hasTz && hasWhen) { + idEnd = whenBegin - 1; + } else { + // If either tz or when are non-numeric, mimic parsePersonIdent behavior and + // set them both to zero. + tz = 0; + when = 0; + if (hasTz && !hasWhen) { + // Only one trailing numeric field; assume User ID ends before this + // field, but discard its value. + idEnd = tzBegin - 1; + } else { + // No trailing numeric fields; User ID is whole raw value. + idEnd = raw.length; + } + } + String id = new String(raw, 0, idEnd, UTF_8); + + return new PushCertificateIdent(str, id, when * 1000L, tz); + } + + private final String raw; + private final String userId; + private final long when; + private final int tzOffset; + + /** + * Construct a new identity from an OpenPGP User ID. + * + * @param userId + * OpenPGP User ID; any UTF-8 string. + * @param when + * local time. + * @param tzOffset + * timezone offset; see {@link #getTimeZoneOffset()}. + */ + public PushCertificateIdent(String userId, long when, int tzOffset) { + this.userId = userId; + this.when = when; + this.tzOffset = tzOffset; + StringBuilder sb = new StringBuilder(userId).append(' ').append(when / 1000) + .append(' '); + PersonIdent.appendTimezone(sb, tzOffset); + raw = sb.toString(); + } + + private PushCertificateIdent(String raw, String userId, long when, + int tzOffset) { + this.raw = raw; + this.userId = userId; + this.when = when; + this.tzOffset = tzOffset; + } + + /** + * Get the raw string from which this identity was parsed. + * <p> + * If the string was constructed manually, a suitable canonical string is + * returned. + * <p> + * For the purposes of bytewise comparisons with other OpenPGP IDs, the string + * must be encoded as UTF-8. + * + * @return the raw string. + */ + public String getRaw() { + return raw; + } + + /** @return the OpenPGP User ID, which may be any string. */ + public String getUserId() { + return userId; + } + + /** + * @return the name portion of the User ID. If no email address would be + * parsed by {@link #getEmailAddress()}, returns the full User ID with + * spaces trimmed. + */ + public String getName() { + int nameEnd = userId.indexOf('<'); + if (nameEnd < 0 || userId.indexOf('>', nameEnd) < 0) { + nameEnd = userId.length(); + } + nameEnd--; + while (nameEnd >= 0 && userId.charAt(nameEnd) == ' ') { + nameEnd--; + } + int nameBegin = 0; + while (nameBegin < nameEnd && userId.charAt(nameBegin) == ' ') { + nameBegin++; + } + return userId.substring(nameBegin, nameEnd + 1); + } + + /** + * @return the email portion of the User ID, if one was successfully parsed + * from {@link #getUserId()}, or null. + */ + public String getEmailAddress() { + int emailBegin = userId.indexOf('<'); + if (emailBegin < 0) { + return null; + } + int emailEnd = userId.indexOf('>', emailBegin); + if (emailEnd < 0) { + return null; + } + return userId.substring(emailBegin + 1, emailEnd); + } + + /** @return the timestamp of the identity. */ + public Date getWhen() { + return new Date(when); + } + + /** + * @return this person's declared time zone; null if the timezone is unknown. + */ + public TimeZone getTimeZone() { + return PersonIdent.getTimeZone(tzOffset); + } + + /** + * @return this person's declared time zone as minutes east of UTC. If the + * timezone is to the west of UTC it is negative. + */ + public int getTimeZoneOffset() { + return tzOffset; + } + + @Override + public boolean equals(Object o) { + return (o instanceof PushCertificateIdent) + && raw.equals(((PushCertificateIdent) o).raw); + } + + @Override + public int hashCode() { + return raw.hashCode(); + } + + @SuppressWarnings("nls") + @Override + public String toString() { + SimpleDateFormat fmt; + fmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US); + fmt.setTimeZone(getTimeZone()); + return getClass().getSimpleName() + + "[raw=\"" + raw + "\"," + + " userId=\"" + userId + "\"," + + " " + fmt.format(Long.valueOf(when)) + "]"; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java index 4bb3d6bf82..5174f85627 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java @@ -40,98 +40,305 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT; import java.io.EOFException; import java.io.IOException; +import java.io.Reader; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.BaseReceivePack.ReceiveConfig; +import org.eclipse.jgit.transport.PushCertificate.NonceStatus; +import org.eclipse.jgit.util.IO; /** - * Parser for Push certificates + * Parser for signed push certificates. * * @since 4.0 */ -public class PushCertificateParser extends PushCertificate { +public class PushCertificateParser { + static final String BEGIN_SIGNATURE = + "-----BEGIN PGP SIGNATURE-----"; //$NON-NLS-1$ + static final String END_SIGNATURE = + "-----END PGP SIGNATURE-----"; //$NON-NLS-1$ + + static final String VERSION = "certificate version"; //$NON-NLS-1$ + + static final String PUSHER = "pusher"; //$NON-NLS-1$ + + static final String PUSHEE = "pushee"; //$NON-NLS-1$ + + static final String NONCE = "nonce"; //$NON-NLS-1$ + + static final String END_CERT = "push-cert-end"; //$NON-NLS-1$ + + private static final String VERSION_0_1 = "0.1"; //$NON-NLS-1$ + + private static interface StringReader { + /** + * @return the next string from the input, up to an optional newline, with + * newline stripped if present + * + * @throws EOFException + * if EOF was reached. + * @throws IOException + * if an error occurred during reading. + */ + String read() throws EOFException, IOException; + } + + private static class PacketLineReader implements StringReader { + private final PacketLineIn pckIn; - private static final String VERSION = "version "; //$NON-NLS-1$ + private PacketLineReader(PacketLineIn pckIn) { + this.pckIn = pckIn; + } + + @Override + public String read() throws IOException { + return pckIn.readString(); + } + } + + private static class StreamReader implements StringReader { + private final Reader reader; + + private StreamReader(Reader reader) { + this.reader = reader; + } + + @Override + public String read() throws IOException { + // Presize for a command containing 2 SHA-1s and some refname. + String line = IO.readLine(reader, 41 * 2 + 64); + if (line.isEmpty()) { + throw new EOFException(); + } else if (line.charAt(line.length() - 1) == '\n') { + line = line.substring(0, line.length() - 1); + } + return line; + } + } - private static final String PUSHER = "pusher"; //$NON-NLS-1$ + /** + * Parse a push certificate from a reader. + * <p> + * Differences from the {@link PacketLineIn} receiver methods: + * <ul> + * <li>Does not use pkt-line framing.</li> + * <li>Reads an entire cert in one call rather than depending on a loop in + * the caller.</li> + * <li>Does not assume a {@code "push-cert-end"} line.</li> + * </ul> + * + * @param r + * input reader; consumed only up until the end of the next + * signature in the input. + * @return the parsed certificate, or null if the reader was at EOF. + * @throws PackProtocolException + * if the certificate is malformed. + * @throws IOException + * if there was an error reading from the input. + * @since 4.1 + */ + public static PushCertificate fromReader(Reader r) + throws PackProtocolException, IOException { + return new PushCertificateParser().parse(r); + } - private static final String PUSHEE = "pushee"; //$NON-NLS-1$ + /** + * Parse a push certificate from a string. + * + * @see #fromReader(Reader) + * @param str + * input string. + * @return the parsed certificate. + * @throws PackProtocolException + * if the certificate is malformed. + * @throws IOException + * if there was an error reading from the input. + * @since 4.1 + */ + public static PushCertificate fromString(String str) + throws PackProtocolException, IOException { + return fromReader(new java.io.StringReader(str)); + } - private static final String NONCE = "nonce"; //$NON-NLS-1$ + private boolean received; + private String version; + private PushCertificateIdent pusher; + private String pushee; - /** The individual certificate which is presented to the client */ + /** The nonce that was sent to the client. */ private String sentNonce; /** - * The nonce the pusher signed. This may vary from pushCertNonce See - * git-core documentation for reasons. + * The nonce the pusher signed. + * <p> + * This may vary from {@link #sentNonce}; see git-core documentation for + * reasons. */ private String receivedNonce; + private NonceStatus nonceStatus; + private String signature; + + /** Database we write the push certificate into. */ + private final Repository db; + /** * The maximum time difference which is acceptable between advertised nonce * and received signed nonce. */ - private int nonceSlopLimit; + private final int nonceSlopLimit; - NonceGenerator nonceGenerator; + private final boolean enabled; + private final NonceGenerator nonceGenerator; + private final List<ReceiveCommand> commands = new ArrayList<>(); /** - * used to build up commandlist + * @param into + * destination repository for the push. + * @param cfg + * configuration for signed push. + * @since 4.1 */ - StringBuilder commandlistBuilder; + public PushCertificateParser(Repository into, SignedPushConfig cfg) { + if (cfg != null) { + nonceSlopLimit = cfg.getCertNonceSlopLimit(); + nonceGenerator = cfg.getNonceGenerator(); + } else { + nonceSlopLimit = 0; + nonceGenerator = null; + } + db = into; + enabled = nonceGenerator != null; + } - /** Database we write the push certificate into. */ - private Repository db; + private PushCertificateParser() { + db = null; + nonceSlopLimit = 0; + nonceGenerator = null; + enabled = true; + } - PushCertificateParser(Repository into, ReceiveConfig cfg) { - nonceSlopLimit = cfg.certNonceSlopLimit; - nonceGenerator = cfg.certNonceSeed != null - ? new HMACSHA1NonceGenerator(cfg.certNonceSeed) - : null; - db = into; + /** + * Parse a push certificate from a reader. + * + * @see #fromReader(Reader) + * @param r + * input reader; consumed only up until the end of the next + * signature in the input. + * @return the parsed certificate, or null if the reader was at EOF. + * @throws PackProtocolException + * if the certificate is malformed. + * @throws IOException + * if there was an error reading from the input. + * @since 4.1 + */ + public PushCertificate parse(Reader r) + throws PackProtocolException, IOException { + StreamReader reader = new StreamReader(r); + receiveHeader(reader, true); + String line; + try { + while (!(line = reader.read()).isEmpty()) { + if (line.equals(BEGIN_SIGNATURE)) { + receiveSignature(reader); + break; + } + addCommand(line); + } + } catch (EOFException e) { + // EOF reached, but might have been at a valid state. Let build call below + // sort it out. + } + return build(); + } + + /** + * @return the parsed certificate, or null if push certificates are disabled. + * @throws IOException + * if the push certificate has missing or invalid fields. + * @since 4.1 + */ + public PushCertificate build() throws IOException { + if (!received || !enabled) { + return null; + } + try { + return new PushCertificate(version, pusher, pushee, receivedNonce, + nonceStatus, Collections.unmodifiableList(commands), signature); + } catch (IllegalArgumentException e) { + throw new IOException(e.getMessage(), e); + } } /** - * @return if the server is configured to use signed pushes. + * @return if the repository is configured to use signed pushes in this + * context. + * @since 4.0 */ public boolean enabled() { - return nonceGenerator != null; + return enabled; } /** * @return the whole string for the nonce to be included into the capability - * advertisement. + * advertisement, or null if push certificates are disabled. + * @since 4.0 */ public String getAdvertiseNonce() { - sentNonce = nonceGenerator.createNonce(db, - TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); - return CAPABILITY_PUSH_CERT + "=" + sentNonce; //$NON-NLS-1$ + String nonce = sentNonce(); + if (nonce == null) { + return null; + } + return CAPABILITY_PUSH_CERT + '=' + nonce; + } + + private String sentNonce() { + if (sentNonce == null && nonceGenerator != null) { + sentNonce = nonceGenerator.createNonce(db, + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); + } + return sentNonce; + } + + private static String parseHeader(StringReader reader, String header) + throws IOException { + return parseHeader(reader.read(), header); } - private String parseNextLine(PacketLineIn pckIn, String startingWith) + private static String parseHeader(String s, String header) throws IOException { - String s = pckIn.readString(); - if (!s.startsWith(startingWith)) - throw new IOException(MessageFormat.format( - JGitText.get().errorInvalidPushCert, - "expected " + startingWith)); //$NON-NLS-1$ - return s.substring(startingWith.length()); + if (s.isEmpty()) { + throw new EOFException(); + } + if (s.length() <= header.length() + || !s.startsWith(header) + || s.charAt(header.length()) != ' ') { + throw new PackProtocolException(MessageFormat.format( + JGitText.get().pushCertificateInvalidField, header)); + } + return s.substring(header.length() + 1); } /** * Receive a list of commands from the input encapsulated in a push - * certificate. This method doesn't parse the first line "push-cert \NUL - * <capabilities>", but assumes the first line including the + * certificate. + * <p> + * This method doesn't parse the first line {@code "push-cert \NUL + * <capabilities>"}, but assumes the first line including the * capabilities has already been handled by the caller. * * @param pckIn @@ -144,62 +351,115 @@ public class PushCertificateParser extends PushCertificate { * @throws IOException * if the certificate from the client is badly malformed or the * client disconnects before sending the entire certificate. + * @since 4.0 */ public void receiveHeader(PacketLineIn pckIn, boolean stateless) throws IOException { + receiveHeader(new PacketLineReader(pckIn), stateless); + } + + private void receiveHeader(StringReader reader, boolean stateless) + throws IOException { try { - String version = parseNextLine(pckIn, VERSION); - if (!version.equals("0.1")) { //$NON-NLS-1$ - throw new IOException(MessageFormat.format( - JGitText.get().errorInvalidPushCert, - "version not supported")); //$NON-NLS-1$ + try { + version = parseHeader(reader, VERSION); + } catch (EOFException e) { + return; + } + received = true; + if (!version.equals(VERSION_0_1)) { + throw new PackProtocolException(MessageFormat.format( + JGitText.get().pushCertificateInvalidFieldValue, VERSION, version)); + } + String rawPusher = parseHeader(reader, PUSHER); + pusher = PushCertificateIdent.parse(rawPusher); + if (pusher == null) { + throw new PackProtocolException(MessageFormat.format( + JGitText.get().pushCertificateInvalidFieldValue, + PUSHER, rawPusher)); + } + String next = reader.read(); + if (next.startsWith(PUSHEE)) { + pushee = parseHeader(next, PUSHEE); + receivedNonce = parseHeader(reader, NONCE); + } else { + receivedNonce = parseHeader(next, NONCE); } - pusher = parseNextLine(pckIn, PUSHER); - pushee = parseNextLine(pckIn, PUSHEE); - receivedNonce = parseNextLine(pckIn, NONCE); - // an empty line - if (!pckIn.readString().isEmpty()) { - throw new IOException(MessageFormat.format( - JGitText.get().errorInvalidPushCert, - "expected empty line after header")); //$NON-NLS-1$ + nonceStatus = nonceGenerator != null + ? nonceGenerator.verify( + receivedNonce, sentNonce(), db, stateless, nonceSlopLimit) + : NonceStatus.UNSOLICITED; + // An empty line. + if (!reader.read().isEmpty()) { + throw new PackProtocolException( + JGitText.get().pushCertificateInvalidHeader); } } catch (EOFException eof) { - throw new IOException(MessageFormat.format( - JGitText.get().errorInvalidPushCert, - "broken push certificate header")); //$NON-NLS-1$ + throw new PackProtocolException( + JGitText.get().pushCertificateInvalidHeader, eof); } - nonceStatus = nonceGenerator.verify(receivedNonce, sentNonce, db, - stateless, nonceSlopLimit); } /** - * Reads the gpg signature. This method assumes the line "-----BEGIN PGP - * SIGNATURE-----\n" has already been parsed and continues parsing until an - * "-----END PGP SIGNATURE-----\n" is found. + * Read the PGP signature. + * <p> + * This method assumes the line + * {@code "-----BEGIN PGP SIGNATURE-----"} has already been parsed, + * and continues parsing until an {@code "-----END PGP SIGNATURE-----"} is + * found, followed by {@code "push-cert-end"}. * * @param pckIn * where we read the signature from. * @throws IOException + * if the signature is invalid. + * @since 4.0 */ public void receiveSignature(PacketLineIn pckIn) throws IOException { + StringReader reader = new PacketLineReader(pckIn); + receiveSignature(reader); + if (!reader.read().equals(END_CERT)) { + throw new PackProtocolException( + JGitText.get().pushCertificateInvalidSignature); + } + } + + private void receiveSignature(StringReader reader) throws IOException { + received = true; try { - StringBuilder sig = new StringBuilder(); - String line = pckIn.readStringRaw(); - while (!line.equals("-----END PGP SIGNATURE-----\n")) //$NON-NLS-1$ - sig.append(line); - signature = sig.toString(); - commandList = commandlistBuilder.toString(); + StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n'); + String line; + while (!(line = reader.read()).equals(END_SIGNATURE)) { + sig.append(line).append('\n'); + } + signature = sig.append(END_SIGNATURE).append('\n').toString(); } catch (EOFException eof) { - throw new IOException(MessageFormat.format( - JGitText.get().errorInvalidPushCert, - "broken push certificate signature")); //$NON-NLS-1$ + throw new PackProtocolException( + JGitText.get().pushCertificateInvalidSignature, eof); } } /** - * @param rawLine + * Add a command to the signature. + * + * @param cmd + * the command. + * @since 4.1 + */ + public void addCommand(ReceiveCommand cmd) { + commands.add(cmd); + } + + /** + * Add a command to the signature. + * + * @param line + * the line read from the wire that produced this + * command, with optional trailing newline already trimmed. + * @throws PackProtocolException + * if the raw line cannot be parsed to a command. + * @since 4.0 */ - public void addCommand(String rawLine) { - commandlistBuilder.append(rawLine); + public void addCommand(String line) throws PackProtocolException { + commands.add(parseCommand(line)); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java new file mode 100644 index 0000000000..d8672d5a2b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.FileMode.TYPE_FILE; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +/** + * Storage for recorded push certificates. + * <p> + * Push certificates are stored in a special ref {@code refs/meta/push-certs}. + * The filenames in the tree are ref names followed by the special suffix + * <code>@{cert}</code>, and the contents are the latest push cert affecting + * that ref. The special suffix allows storing certificates for both refs/foo + * and refs/foo/bar in case those both existed at some point. + * + * @since 4.1 + */ +public class PushCertificateStore implements AutoCloseable { + /** Ref name storing push certificates. */ + static final String REF_NAME = + Constants.R_REFS + "meta/push-certs"; //$NON-NLS-1$ + + private static class PendingCert { + private PushCertificate cert; + private PersonIdent ident; + private Collection<ReceiveCommand> matching; + + private PendingCert(PushCertificate cert, PersonIdent ident, + Collection<ReceiveCommand> matching) { + this.cert = cert; + this.ident = ident; + this.matching = matching; + } + } + + private final Repository db; + private final List<PendingCert> pending; + private ObjectReader reader; + private RevCommit commit; + + /** + * Create a new store backed by the given repository. + * + * @param db + * the repository. + */ + public PushCertificateStore(Repository db) { + this.db = db; + pending = new ArrayList<>(); + } + + /** + * Close resources opened by this store. + * <p> + * If {@link #get(String)} was called, closes the cached object reader created + * by that method. Does not close the underlying repository. + */ + public void close() { + if (reader != null) { + reader.close(); + reader = null; + commit = null; + } + } + + /** + * Get latest push certificate associated with a ref. + * <p> + * Lazily opens {@code refs/meta/push-certs} and reads from the repository as + * necessary. The state is cached between calls to {@code get}; to reread the, + * call {@link #close()} first. + * + * @param refName + * the ref name to get the certificate for. + * @return last certificate affecting the ref, or null if no cert was recorded + * for the last update to this ref. + * @throws IOException + * if a problem occurred reading the repository. + */ + public PushCertificate get(String refName) throws IOException { + if (reader == null) { + load(); + } + try (TreeWalk tw = newTreeWalk(refName)) { + return read(tw); + } + } + + /** + * Iterate over all push certificates affecting a ref. + * <p> + * Only includes push certificates actually stored in the tree; see class + * Javadoc for conditions where this might not include all push certs ever + * seen for this ref. + * <p> + * The returned iterable may be iterated multiple times, and push certs will + * be re-read from the current state of the store on each call to {@link + * Iterable#iterator()}. However, method calls on the returned iterator may + * fail if {@code save} or {@code close} is called on the enclosing store + * during iteration. + * + * @param refName + * the ref name to get certificates for. + * @return iterable over certificates; must be fully iterated in order to + * close resources. + */ + public Iterable<PushCertificate> getAll(final String refName) { + return new Iterable<PushCertificate>() { + @Override + public Iterator<PushCertificate> iterator() { + return new Iterator<PushCertificate>() { + private final String path = pathName(refName); + private PushCertificate next; + + private RevWalk rw; + { + try { + if (reader == null) { + load(); + } + if (commit != null) { + rw = new RevWalk(reader); + rw.setTreeFilter(AndTreeFilter.create( + PathFilterGroup.create( + Collections.singleton(PathFilter.create(path))), + TreeFilter.ANY_DIFF)); + rw.setRewriteParents(false); + rw.markStart(rw.parseCommit(commit)); + } else { + rw = null; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean hasNext() { + try { + if (next == null) { + if (rw == null) { + return false; + } + try { + RevCommit c = rw.next(); + if (c != null) { + try (TreeWalk tw = TreeWalk.forPath( + rw.getObjectReader(), path, c.getTree())) { + next = read(tw); + } + } else { + next = null; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return next != null; + } finally { + if (next == null && rw != null) { + rw.close(); + rw = null; + } + } + } + + @Override + public PushCertificate next() { + hasNext(); + PushCertificate n = next; + if (n == null) { + throw new NoSuchElementException(); + } + next = null; + return n; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + private void load() throws IOException { + close(); + reader = db.newObjectReader(); + Ref ref = db.getRefDatabase().exactRef(REF_NAME); + if (ref == null) { + // No ref, same as empty. + return; + } + try (RevWalk rw = new RevWalk(reader)) { + commit = rw.parseCommit(ref.getObjectId()); + } + } + + private static PushCertificate read(TreeWalk tw) throws IOException { + if (tw == null || (tw.getRawMode(0) & TYPE_FILE) != TYPE_FILE) { + return null; + } + ObjectLoader loader = + tw.getObjectReader().open(tw.getObjectId(0), OBJ_BLOB); + try (InputStream in = loader.openStream(); + Reader r = new BufferedReader(new InputStreamReader(in, UTF_8))) { + return PushCertificateParser.fromReader(r); + } + } + + /** + * Put a certificate to be saved to the store. + * <p> + * Writes the contents of this certificate for each ref mentioned. It is up to + * the caller to ensure this certificate accurately represents the state of + * the ref. + * <p> + * Pending certificates added to this method are not returned by {@link + * #get(String)} and {@link #getAll(String)} until after calling {@link + * #save()}. + * + * @param cert + * certificate to store. + * @param ident + * identity for the commit that stores this certificate. Pending + * certificates are sorted by identity timestamp during {@link + * #save()}. + */ + public void put(PushCertificate cert, PersonIdent ident) { + put(cert, ident, null); + } + + /** + * Put a certificate to be saved to the store, matching a set of commands. + * <p> + * Like {@link #put(PushCertificate, PersonIdent)}, except a value is only + * stored for a push certificate if there is a corresponding command in the + * list that exactly matches the old/new values mentioned in the push + * certificate. + * <p> + * Pending certificates added to this method are not returned by {@link + * #get(String)} and {@link #getAll(String)} until after calling {@link + * #save()}. + * + * @param cert + * certificate to store. + * @param ident + * identity for the commit that stores this certificate. Pending + * certificates are sorted by identity timestamp during {@link + * #save()}. + * @param matching + * only store certs for the refs listed in this list whose values + * match the commands in the cert. + */ + public void put(PushCertificate cert, PersonIdent ident, + Collection<ReceiveCommand> matching) { + pending.add(new PendingCert(cert, ident, matching)); + } + + /** + * Save pending certificates to the store. + * <p> + * One commit is created per certificate added with {@link + * #put(PushCertificate, PersonIdent)}, in order of identity timestamps, and + * a single ref update is performed. + * <p> + * The pending list is cleared if and only the ref update fails, which allows + * for easy retries in case of lock failure. + * + * @return the result of attempting to update the ref. + * @throws IOException + * if there was an error reading from or writing to the + * repository. + */ + public RefUpdate.Result save() throws IOException { + ObjectId newId = write(); + if (newId == null) { + return RefUpdate.Result.NO_CHANGE; + } + try (ObjectInserter inserter = db.newObjectInserter()) { + RefUpdate.Result result = updateRef(newId); + switch (result) { + case FAST_FORWARD: + case NEW: + case NO_CHANGE: + pending.clear(); + break; + default: + break; + } + return result; + } finally { + close(); + } + } + + /** + * Save pending certificates to the store in an existing batch ref update. + * <p> + * One commit is created per certificate added with {@link + * #put(PushCertificate, PersonIdent)}, in order of identity timestamps, all + * commits are flushed, and a single command is added to the batch. + * <p> + * The cached ref value and pending list are <em>not</em> cleared. If the ref + * update succeeds, the caller is responsible for calling {@link #close()} + * and/or {@link #clear()}. + * + * @param batch + * update to save to. + * @return whether a command was added to the batch. + * @throws IOException + * if there was an error reading from or writing to the + * repository. + */ + public boolean save(BatchRefUpdate batch) throws IOException { + ObjectId newId = write(); + if (newId == null || newId.equals(commit)) { + return false; + } + batch.addCommand(new ReceiveCommand( + commit != null ? commit : ObjectId.zeroId(), newId, REF_NAME)); + return true; + } + + /** + * Clear pending certificates added with {@link #put(PushCertificate, + * PersonIdent)}. + */ + public void clear() { + pending.clear(); + } + + private ObjectId write() throws IOException { + if (pending.isEmpty()) { + return null; + } + if (reader == null) { + load(); + } + sortPending(pending); + + ObjectId curr = commit; + DirCache dc = newDirCache(); + try (ObjectInserter inserter = db.newObjectInserter()) { + for (PendingCert pc : pending) { + curr = saveCert(inserter, dc, pc, curr); + } + inserter.flush(); + return curr; + } + } + + private static void sortPending(List<PendingCert> pending) { + Collections.sort(pending, new Comparator<PendingCert>() { + @Override + public int compare(PendingCert a, PendingCert b) { + return Long.signum( + a.ident.getWhen().getTime() - b.ident.getWhen().getTime()); + } + }); + } + + private DirCache newDirCache() throws IOException { + DirCache dc = DirCache.newInCore(); + if (commit != null) { + DirCacheBuilder b = dc.builder(); + b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, commit.getTree()); + b.finish(); + } + return dc; + } + + private ObjectId saveCert(ObjectInserter inserter, DirCache dc, + PendingCert pc, ObjectId curr) throws IOException { + Map<String, ReceiveCommand> byRef; + if (pc.matching != null) { + byRef = new HashMap<>(); + for (ReceiveCommand cmd : pc.matching) { + if (byRef.put(cmd.getRefName(), cmd) != null) { + throw new IllegalStateException(); + } + } + } else { + byRef = null; + } + + DirCacheEditor editor = dc.editor(); + String certText = pc.cert.toText() + pc.cert.getSignature(); + final ObjectId certId = inserter.insert(OBJ_BLOB, certText.getBytes(UTF_8)); + boolean any = false; + for (ReceiveCommand cmd : pc.cert.getCommands()) { + if (byRef != null && !commandsEqual(cmd, byRef.get(cmd.getRefName()))) { + continue; + } + any = true; + editor.add(new PathEdit(pathName(cmd.getRefName())) { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.REGULAR_FILE); + ent.setObjectId(certId); + } + }); + } + if (!any) { + return curr; + } + editor.finish(); + CommitBuilder cb = new CommitBuilder(); + cb.setAuthor(pc.ident); + cb.setCommitter(pc.ident); + cb.setTreeId(dc.writeTree(inserter)); + if (curr != null) { + cb.setParentId(curr); + } else { + cb.setParentIds(Collections.<ObjectId> emptyList()); + } + cb.setMessage(buildMessage(pc.cert)); + return inserter.insert(OBJ_COMMIT, cb.build()); + } + + private static boolean commandsEqual(ReceiveCommand c1, ReceiveCommand c2) { + if (c1 == null || c2 == null) { + return c1 == c2; + } + return c1.getRefName().equals(c2.getRefName()) + && c1.getOldId().equals(c2.getOldId()) + && c1.getNewId().equals(c2.getNewId()); + } + + private RefUpdate.Result updateRef(ObjectId newId) throws IOException { + RefUpdate ru = db.updateRef(REF_NAME); + ru.setExpectedOldObjectId(commit != null ? commit : ObjectId.zeroId()); + ru.setNewObjectId(newId); + ru.setRefLogIdent(pending.get(pending.size() - 1).ident); + ru.setRefLogMessage(JGitText.get().storePushCertReflog, false); + try (RevWalk rw = new RevWalk(reader)) { + return ru.update(rw); + } + } + + private TreeWalk newTreeWalk(String refName) throws IOException { + if (commit == null) { + return null; + } + return TreeWalk.forPath(reader, pathName(refName), commit.getTree()); + } + + private static String pathName(String refName) { + return refName + "@{cert}"; //$NON-NLS-1$ + } + + private static String buildMessage(PushCertificate cert) { + StringBuilder sb = new StringBuilder(); + if (cert.getCommands().size() == 1) { + sb.append(MessageFormat.format( + JGitText.get().storePushCertOneRef, + cert.getCommands().get(0).getRefName())); + } else { + sb.append(MessageFormat.format( + JGitText.get().storePushCertMultipleRefs, + Integer.valueOf(cert.getCommands().size()))); + } + return sb.append('\n').toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 44b8778eea..6ed9bc8f17 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -246,6 +246,7 @@ public class ReceivePack extends BaseReceivePack { try { postReceive.onPostReceive(this, filterCommands(Result.OK)); } catch (Throwable e) { + // empty } throw new UnpackException(unpackError); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java new file mode 100644 index 0000000000..942e7d7742 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Config.SectionParser; + +/** + * Configuration for server-side signed push verification. + * + * @since 4.1 + */ +public class SignedPushConfig { + /** Key for {@link Config#get(SectionParser)}. */ + public static final SectionParser<SignedPushConfig> KEY = + new SectionParser<SignedPushConfig>() { + public SignedPushConfig parse(Config cfg) { + return new SignedPushConfig(cfg); + } + }; + + private String certNonceSeed; + private int certNonceSlopLimit; + private NonceGenerator nonceGenerator; + + /** Create a new config with default values disabling push verification. */ + public SignedPushConfig() { + } + + SignedPushConfig(Config cfg) { + setCertNonceSeed(cfg.getString("receive", null, "certnonceseed")); //$NON-NLS-1$ //$NON-NLS-2$ + certNonceSlopLimit = cfg.getInt("receive", "certnonceslop", 0); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Set the seed used by the nonce verifier. + * <p> + * Setting this to a non-null value enables push certificate verification + * using the default {@link HMACSHA1NonceGenerator} implementation, if a + * different implementation was not set using {@link + * #setNonceGenerator(NonceGenerator)}. + * + * @param seed + * new seed value. + */ + public void setCertNonceSeed(String seed) { + certNonceSeed = seed; + } + + /** @return the configured seed. */ + public String getCertNonceSeed() { + return certNonceSeed; + } + + /** + * Set the nonce slop limit. + * <p> + * Old but valid nonces within this limit will be accepted. + * + * @param limit + * new limit in seconds. + */ + public void setCertNonceSlopLimit(int limit) { + certNonceSlopLimit = limit; + } + + /** @return the configured nonce slop limit. */ + public int getCertNonceSlopLimit() { + return certNonceSlopLimit; + } + + /** + * Set the {@link NonceGenerator} used for signed pushes. + * <p> + * Setting this to a non-null value enables push certificate verification. If + * this method is called, this implementation will be used instead of the + * default {@link HMACSHA1NonceGenerator} even if {@link + * #setCertNonceSeed(String)} was called. + * + * @param generator + * new nonce generator. + */ + public void setNonceGenerator(NonceGenerator generator) { + nonceGenerator = generator; + } + + /** + * Get the {@link NonceGenerator} used for signed pushes. + * <p> + * If {@link #setNonceGenerator(NonceGenerator)} was used to set a non-null + * implementation, that will be returned. If no custom implementation was set + * but {@link #setCertNonceSeed(String)} was called, returns a newly-created + * {@link HMACSHA1NonceGenerator}. + * + * @return the configured nonce generator. + */ + public NonceGenerator getNonceGenerator() { + if (nonceGenerator != null) { + return nonceGenerator; + } else if (certNonceSeed != null) { + return new HMACSHA1NonceGenerator(certNonceSeed); + } + return null; + } +} 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 138002e63e..f4de82147d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -71,6 +71,7 @@ public class TransferConfig { private final boolean safeForWindows; private final boolean safeForMacOS; private final boolean allowTipSha1InWant; + private final boolean allowReachableSha1InWant; private final String[] hideRefs; TransferConfig(final Repository db) { @@ -94,6 +95,8 @@ public class TransferConfig { allowTipSha1InWant = rc.getBoolean( "uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$ + allowReachableSha1InWant = rc.getBoolean( + "uploadpack", "allowreachablesha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$ hideRefs = rc.getStringList("uploadpack", null, "hiderefs"); //$NON-NLS-1$ //$NON-NLS-2$ } @@ -121,6 +124,14 @@ public class TransferConfig { } /** + * @return allow clients to request non-tip SHA-1s? + * @since 4.1 + */ + public boolean isAllowReachableSha1InWant() { + return allowReachableSha1InWant; + } + + /** * @return {@link RefFilter} respecting configured hidden refs. * @since 3.1 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java index afaaa69a43..745cdb72d5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java @@ -148,7 +148,7 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport { super(local, uri); Properties props = loadProperties(); - if (!props.contains("tmpdir") && local.getDirectory() != null) //$NON-NLS-1$ + if (!props.containsKey("tmpdir") && local.getDirectory() != null) //$NON-NLS-1$ props.put("tmpdir", local.getDirectory().getPath()); //$NON-NLS-1$ s3 = new AmazonS3(props); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java index 91e212b478..3700b49555 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java @@ -4,6 +4,7 @@ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> * Copyright (C) 2013, Robin Stocker <robin@nibor.org> + * Copyright (C) 2015, Patrick Steinhardt <ps@pks.im> * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -134,11 +135,11 @@ public class URIish implements Serializable { + OPT_USER_PWD_P // + HOST_P // + OPT_PORT_P // - + "(" // open a catpuring group the the user-home-dir part //$NON-NLS-1$ - + (USER_HOME_P + "?") // //$NON-NLS-1$ - + "[\\\\/])" // //$NON-NLS-1$ + + "(" // open a group capturing the user-home-dir-part //$NON-NLS-1$ + + (USER_HOME_P + "?") //$NON-NLS-1$ + + "[\\\\/])" //$NON-NLS-1$ + ")?" // close the optional group containing hostname //$NON-NLS-1$ - + "(.+)?" // //$NON-NLS-1$ + + "(.+)?" //$NON-NLS-1$ + "$"); //$NON-NLS-1$ /** @@ -690,6 +691,10 @@ public class URIish implements Serializable { * <td><code>/path/to/repo/</code></td> * </tr> * <tr> + * <td><code>localhost</code></td> + * <td><code>ssh://localhost/</code></td> + * </tr> + * <tr> * <td><code>/path//to</code></td> * <td>an empty string</td> * </tr> @@ -703,9 +708,12 @@ public class URIish implements Serializable { * @see #getPath */ public String getHumanishName() throws IllegalArgumentException { - if ("".equals(getPath()) || getPath() == null) //$NON-NLS-1$ - throw new IllegalArgumentException(); String s = getPath(); + if ("/".equals(s)) //$NON-NLS-1$ + s = getHost(); + if ("".equals(s) || s == null) //$NON-NLS-1$ + throw new IllegalArgumentException(); + String[] elements; if ("file".equals(scheme) || LOCAL_FILE.matcher(s).matches()) //$NON-NLS-1$ elements = s.split("[\\" + File.separatorChar + "/]"); //$NON-NLS-1$ //$NON-NLS-2$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 753277dd3f..101057fb4f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -46,6 +46,7 @@ package org.eclipse.jgit.transport; import static org.eclipse.jgit.lib.RefDatabase.ALL; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK_DETAILED; @@ -93,6 +94,7 @@ import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.storage.pack.PackStatistics; import org.eclipse.jgit.transport.GitProtocolConstants.MultiAck; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.util.io.InterruptTimer; @@ -252,6 +254,9 @@ public class UploadPack { /** Hook handling the various upload phases. */ private PreUploadHook preUploadHook = PreUploadHook.NULL; + /** Hook for taking post upload actions. */ + private PostUploadHook postUploadHook = PostUploadHook.NULL; + /** Capabilities requested by the client. */ private Set<String> options; String userAgent; @@ -305,7 +310,7 @@ public class UploadPack { private boolean noDone; - private PackWriter.Statistics statistics; + private PackStatistics statistics; private UploadPackLogger logger = UploadPackLogger.NULL; @@ -518,7 +523,7 @@ public class UploadPack { this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT; } - /** @return the configured upload hook. */ + /** @return the configured pre upload hook. */ public PreUploadHook getPreUploadHook() { return preUploadHook; } @@ -534,6 +539,25 @@ public class UploadPack { } /** + * @return the configured post upload hook. + * @since 4.1 + */ + public PostUploadHook getPostUploadHook() { + return postUploadHook; + } + + /** + * Set the hook for post upload actions (logging, repacking). + * + * @param hook + * the hook; if null no special actions are taken. + * @since 4.1 + */ + public void setPostUploadHook(PostUploadHook hook) { + postUploadHook = hook != null ? hook : PostUploadHook.NULL; + } + + /** * Set the configuration used by the pack generator. * * @param pc @@ -552,11 +576,21 @@ public class UploadPack { */ public void setTransferConfig(TransferConfig tc) { this.transferConfig = tc != null ? tc : new TransferConfig(db); - setRequestPolicy(transferConfig.isAllowTipSha1InWant() - ? RequestPolicy.TIP : RequestPolicy.ADVERTISED); + if (transferConfig.isAllowTipSha1InWant()) { + setRequestPolicy(transferConfig.isAllowReachableSha1InWant() + ? RequestPolicy.REACHABLE_COMMIT_TIP : RequestPolicy.TIP); + } else { + setRequestPolicy(transferConfig.isAllowReachableSha1InWant() + ? RequestPolicy.REACHABLE_COMMIT : RequestPolicy.ADVERTISED); + } } - /** @return the configured logger. */ + /** + * @return the configured logger. + * + * @deprecated Use {@link #getPreUploadHook()}. + */ + @Deprecated public UploadPackLogger getLogger() { return logger; } @@ -566,7 +600,9 @@ public class UploadPack { * * @param logger * the logger instance. If null, no logging occurs. + * @deprecated Use {@link #setPreUploadHook(PreUploadHook)}. */ + @Deprecated public void setLogger(UploadPackLogger logger) { this.logger = logger; } @@ -648,8 +684,23 @@ public class UploadPack { * was sent, such as during the negotation phase of a smart HTTP * connection, or if the client was already up-to-date. * @since 3.0 + * @deprecated Use {@link #getStatistics()}. */ + @Deprecated public PackWriter.Statistics getPackStatistics() { + return statistics == null ? null + : new PackWriter.Statistics(statistics); + } + + /** + * Get the PackWriter's statistics if a pack was sent to the client. + * + * @return statistics about pack output, if a pack was sent. Null if no pack + * was sent, such as during the negotation phase of a smart HTTP + * connection, or if the client was already up-to-date. + * @since 4.1 + */ + public PackStatistics getStatistics() { return statistics; } @@ -684,6 +735,8 @@ public class UploadPack { else multiAck = MultiAck.OFF; + if (!clientShallowCommits.isEmpty()) + verifyClientShallow(); if (depth != 0) processShallow(); if (!clientShallowCommits.isEmpty()) @@ -769,6 +822,35 @@ public class UploadPack { pckOut.end(); } + private void verifyClientShallow() + throws IOException, PackProtocolException { + AsyncRevObjectQueue q = walk.parseAny(clientShallowCommits, true); + try { + for (;;) { + try { + // Shallow objects named by the client must be commits. + RevObject o = q.next(); + if (o == null) { + break; + } + if (!(o instanceof RevCommit)) { + throw new PackProtocolException( + MessageFormat.format( + JGitText.get().invalidShallowObject, + o.name())); + } + } catch (MissingObjectException notCommit) { + // shallow objects not known at the server are ignored + // by git-core upload-pack, match that behavior. + clientShallowCommits.remove(notCommit.getObjectId()); + continue; + } + } + } finally { + q.release(); + } + } + /** * Generate an advertisement of available refs and capabilities. * @@ -808,6 +890,10 @@ public class UploadPack { || policy == RequestPolicy.REACHABLE_COMMIT_TIP || policy == null) adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT); + if (policy == RequestPolicy.REACHABLE_COMMIT + || policy == RequestPolicy.REACHABLE_COMMIT_TIP + || policy == null) + adv.advertiseCapability(OPTION_ALLOW_REACHABLE_SHA1_IN_WANT); adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); adv.setDerefTags(true); Map<String, Ref> advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs(); @@ -1389,6 +1475,7 @@ public class UploadPack { pw.setIndexDisabled(true); pw.setUseCachedPacks(true); pw.setUseBitmaps(depth == 0 && clientShallowCommits.isEmpty()); + pw.setClientShallowCommits(clientShallowCommits); pw.setReuseDeltaCommits(true); pw.setDeltaBaseAsOffset(options.contains(OPTION_OFS_DELTA)); pw.setThin(options.contains(OPTION_THIN_PACK)); @@ -1458,8 +1545,10 @@ public class UploadPack { } finally { statistics = pw.getStatistics(); - if (statistics != null) - logger.onPackStatistics(statistics); + if (statistics != null) { + postUploadHook.onPostUpload(statistics); + logger.onPackStatistics(new PackWriter.Statistics(statistics)); + } pw.close(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java index 99fa6e02bd..85ebecc450 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLogger.java @@ -52,7 +52,10 @@ import org.eclipse.jgit.internal.storage.pack.PackWriter; * thread to a particular connection, if they need to also include connection * information. One method is to use a {@link java.lang.ThreadLocal} to remember * the connection information before invoking UploadPack. + * + * @deprecated use {@link PostUploadHook} instead */ +@Deprecated public interface UploadPackLogger { /** A simple no-op logger. */ public static final UploadPackLogger NULL = new UploadPackLogger() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java index 3f14cc6f58..4ea0319d9c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPackLoggerChain.java @@ -48,10 +48,13 @@ import java.util.List; import org.eclipse.jgit.internal.storage.pack.PackWriter; /** - * {@link UploadPackLogger} that delegates to a list of other loggers. + * UploadPackLogger that delegates to a list of other loggers. * <p> * loggers are run in the order passed to the constructor. + * + * @deprecated Use {@link PostUploadHookChain} instead. */ +@Deprecated public class UploadPackLoggerChain implements UploadPackLogger { private final UploadPackLogger[] loggers; private final int count; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WriteAbortedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WriteAbortedException.java new file mode 100644 index 0000000000..267bf7a380 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WriteAbortedException.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import java.io.IOException; + +/** + * An exception to be thrown when the write operation is aborted. + * <p> + * That can be thrown inside + * {@link ObjectCountCallback#setObjectCount(long)}. + * + * @since 4.1 + */ +public class WriteAbortedException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Construct a {@code WriteAbortedException}. + */ + public WriteAbortedException() { + } + + /** + * Construct a {@code WriteAbortedException}. + * + * @param s message describing the issue + */ + public WriteAbortedException(String s) { + super(s); + } + + /** + * Construct a {@code WriteAbortedException}. + * + * @param s + * message describing the issue + * @param why + * a lower level implementation specific issue. + */ + public WriteAbortedException(String s, Throwable why) { + super(s, why); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java index a2c6edc828..57a61928b5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotAuthorizedException.java @@ -45,12 +45,35 @@ package org.eclipse.jgit.transport.resolver; import org.eclipse.jgit.internal.JGitText; -/** Indicates the request service is not authorized for current user. */ +/** + * Indicates that the requested service requires authentication that + * the current user has not provided. + * <p> + * This corresponds to response code + * {@code HttpServletResponse.SC_UNAUTHORIZED}. + */ public class ServiceNotAuthorizedException extends Exception { private static final long serialVersionUID = 1L; - /** Indicates the request service is not available. */ + /** + * @param message + * @param cause + * @since 4.1 + */ + public ServiceNotAuthorizedException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message + * @since 4.1 + */ + public ServiceNotAuthorizedException(String message) { + super(message); + } + + /** Indicates that the requested service requires authentication. */ public ServiceNotAuthorizedException() { - super(JGitText.get().serviceNotPermittedNoName); + super(JGitText.get().unauthorized); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java index d0fa758a8c..78ae303ed0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/ServiceNotEnabledException.java @@ -49,6 +49,23 @@ import org.eclipse.jgit.internal.JGitText; public class ServiceNotEnabledException extends Exception { private static final long serialVersionUID = 1L; + /** + * @param message + * @param cause + * @since 4.1 + */ + public ServiceNotEnabledException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message + * @since 4.1 + */ + public ServiceNotEnabledException(String message) { + super(message); + } + /** Indicates the request service is not available. */ public ServiceNotEnabledException() { super(JGitText.get().serviceNotEnabledNoName); 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 d7c93d1a1c..73ab04f9cb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -596,7 +596,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { * a relevant ignore rule file exists but cannot be read. */ protected boolean isEntryIgnored(final int pLen) throws IOException { - return isEntryIgnored(pLen, false); + return isEntryIgnored(pLen, mode, false); } /** @@ -605,13 +605,16 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { * * @param pLen * the length of the path in the path buffer. + * @param fileMode + * the original iterator file mode * @param negatePrevious * true if the previous matching iterator rule was negation * @return true if the entry is ignored by an ignore rule. * @throws IOException * a relevant ignore rule file exists but cannot be read. */ - private boolean isEntryIgnored(final int pLen, boolean negatePrevious) + private boolean isEntryIgnored(final int pLen, int fileMode, + boolean negatePrevious) throws IOException { IgnoreNode rules = getIgnoreNode(); if (rules != null) { @@ -623,7 +626,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { if (0 < pOff) pOff--; String p = TreeWalk.pathOf(path, pOff, pLen); - switch (rules.isIgnored(p, FileMode.TREE.equals(mode), + switch (rules.isIgnored(p, FileMode.TREE.equals(fileMode), negatePrevious)) { case IGNORED: return true; @@ -638,7 +641,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } if (parent instanceof WorkingTreeIterator) - return ((WorkingTreeIterator) parent).isEntryIgnored(pLen, + return ((WorkingTreeIterator) parent).isEntryIgnored(pLen, fileMode, negatePrevious); return false; } 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 12dfe96b05..e5219b2a92 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -67,7 +67,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.errors.SymlinksNotSupportedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; @@ -248,7 +247,7 @@ public abstract class FS { * @since 3.0 */ public long lastModified(File f) throws IOException { - return f.lastModified(); + return FileUtils.lastModified(f); } /** @@ -261,7 +260,7 @@ public abstract class FS { * @since 3.0 */ public void setLastModified(File f, long time) throws IOException { - f.setLastModified(time); + FileUtils.setLastModified(f, time); } /** @@ -274,7 +273,7 @@ public abstract class FS { * @since 3.0 */ public long length(File path) throws IOException { - return path.length(); + return FileUtils.getLength(path); } /** @@ -286,9 +285,7 @@ public abstract class FS { * @since 3.3 */ public void delete(File f) throws IOException { - if (!f.delete()) - throw new IOException(MessageFormat.format( - JGitText.get().deleteFileFailed, f.getAbsolutePath())); + FileUtils.delete(f); } /** @@ -623,8 +620,7 @@ public abstract class FS { * @since 3.0 */ public String readSymLink(File path) throws IOException { - throw new SymlinksNotSupportedException( - JGitText.get().errorSymlinksNotSupported); + return FileUtils.readSymLink(path); } /** @@ -634,7 +630,7 @@ public abstract class FS { * @since 3.0 */ public boolean isSymLink(File path) throws IOException { - return false; + return FileUtils.isSymlink(path); } /** @@ -646,7 +642,7 @@ public abstract class FS { * @since 3.0 */ public boolean exists(File path) { - return path.exists(); + return FileUtils.exists(path); } /** @@ -658,7 +654,7 @@ public abstract class FS { * @since 3.0 */ public boolean isDirectory(File path) { - return path.isDirectory(); + return FileUtils.isDirectory(path); } /** @@ -670,7 +666,7 @@ public abstract class FS { * @since 3.0 */ public boolean isFile(File path) { - return path.isFile(); + return FileUtils.isFile(path); } /** @@ -681,7 +677,7 @@ public abstract class FS { * @since 3.0 */ public boolean isHidden(File path) throws IOException { - return path.isHidden(); + return FileUtils.isHidden(path); } /** @@ -693,9 +689,7 @@ public abstract class FS { * @since 3.0 */ public void setHidden(File path, boolean hidden) throws IOException { - if (!path.getName().startsWith(".")) //$NON-NLS-1$ - throw new IllegalArgumentException( - JGitText.get().hiddenFilesStartWithDot); + FileUtils.setHidden(path, hidden); } /** @@ -707,8 +701,7 @@ public abstract class FS { * @since 3.0 */ public void createSymLink(File path, String target) throws IOException { - throw new SymlinksNotSupportedException( - JGitText.get().errorSymlinksNotSupported); + FileUtils.createSymLink(path, target); } /** @@ -866,7 +859,10 @@ public abstract class FS { * @since 4.0 */ public File findHook(Repository repository, final String hookName) { - final File hookFile = new File(new File(repository.getDirectory(), + File gitDir = repository.getDirectory(); + if (gitDir == null) + return null; + final File hookFile = new File(new File(gitDir, Constants.HOOKS), hookName); return hookFile.isFile() ? hookFile : null; } @@ -1067,28 +1063,28 @@ public abstract class FS { return lastModifiedTime; } - private boolean isDirectory; + private final boolean isDirectory; - private boolean isSymbolicLink; + private final boolean isSymbolicLink; - private boolean isRegularFile; + private final boolean isRegularFile; - private long creationTime; + private final long creationTime; - private long lastModifiedTime; + private final long lastModifiedTime; - private boolean isExecutable; + private final boolean isExecutable; - private File file; + private final File file; - private boolean exists; + private final boolean exists; /** * file length */ protected long length = -1; - FS fs; + final FS fs; Attributes(FS fs, File file, boolean exists, boolean isDirectory, boolean isExecutable, boolean isSymbolicLink, @@ -1107,14 +1103,14 @@ public abstract class FS { } /** - * Constructor when there are issues with reading + * Constructor when there are issues with reading. All attributes except + * given will be set to the default values. * * @param fs * @param path */ public Attributes(File path, FS fs) { - this.file = path; - this.fs = fs; + this(fs, path, false, false, false, false, false, 0L, 0L, 0L); } /** 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 b07f8594db..779b10e1bb 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 @@ -168,7 +168,7 @@ public class FS_POSIX extends FS { @Override public boolean canExecute(File f) { - return FileUtil.canExecute(f); + return FileUtils.canExecute(f); } @Override @@ -246,71 +246,16 @@ public class FS_POSIX extends FS { } @Override - public boolean isSymLink(File path) throws IOException { - return FileUtil.isSymlink(path); - } - - @Override - public long lastModified(File path) throws IOException { - return FileUtil.lastModified(path); - } - - @Override - public void setLastModified(File path, long time) throws IOException { - FileUtil.setLastModified(path, time); - } - - @Override - public void delete(File path) throws IOException { - FileUtil.delete(path); - } - - @Override - public long length(File f) throws IOException { - return FileUtil.getLength(f); - } - - @Override - public boolean exists(File path) { - return FileUtil.exists(path); - } - - @Override - public boolean isDirectory(File path) { - return FileUtil.isDirectory(path); - } - - @Override - public boolean isFile(File path) { - return FileUtil.isFile(path); - } - - @Override - public boolean isHidden(File path) throws IOException { - return FileUtil.isHidden(path); - } - - @Override public void setHidden(File path, boolean hidden) throws IOException { // no action on POSIX } - @Override - public String readSymLink(File path) throws IOException { - return FileUtil.readSymlink(path); - } - - @Override - public void createSymLink(File path, String target) throws IOException { - FileUtil.createSymLink(path, target); - } - /** * @since 3.3 */ @Override public Attributes getAttributes(File path) { - return FileUtil.getFileAttributesPosix(this, path); + return FileUtils.getFileAttributesPosix(this, path); } /** @@ -318,7 +263,7 @@ public class FS_POSIX extends FS { */ @Override public File normalize(File file) { - return FileUtil.normalize(file); + return FileUtils.normalize(file); } /** @@ -326,7 +271,7 @@ public class FS_POSIX extends FS { */ @Override public String normalize(String name) { - return FileUtil.normalize(name); + return FileUtils.normalize(name); } /** @@ -335,6 +280,9 @@ public class FS_POSIX extends FS { @Override public File findHook(Repository repository, String hookName) { final File gitdir = repository.getDirectory(); + if (gitdir == null) { + return null; + } final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS) .resolve(hookName); if (Files.isExecutable(hookPath)) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java index 5c652be18a..f0a2e721a6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java @@ -168,7 +168,7 @@ public class FS_Win32 extends FS { try { tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$ File linkName = new File(tempFile.getParentFile(), "tempsymlink"); //$NON-NLS-1$ - FileUtil.createSymLink(linkName, tempFile.getPath()); + createSymLink(linkName, tempFile.getPath()); supportSymlinks = Boolean.TRUE; linkName.delete(); } catch (IOException | UnsupportedOperationException e) { @@ -183,71 +183,11 @@ public class FS_Win32 extends FS { } } - @Override - public boolean isSymLink(File path) throws IOException { - return FileUtil.isSymlink(path); - } - - @Override - public long lastModified(File path) throws IOException { - return FileUtil.lastModified(path); - } - - @Override - public void setLastModified(File path, long time) throws IOException { - FileUtil.setLastModified(path, time); - } - - @Override - public void delete(File path) throws IOException { - FileUtil.delete(path); - } - - @Override - public long length(File f) throws IOException { - return FileUtil.getLength(f); - } - - @Override - public boolean exists(File path) { - return FileUtil.exists(path); - } - - @Override - public boolean isDirectory(File path) { - return FileUtil.isDirectory(path); - } - - @Override - public boolean isFile(File path) { - return FileUtil.isFile(path); - } - - @Override - public boolean isHidden(File path) throws IOException { - return FileUtil.isHidden(path); - } - - @Override - public void setHidden(File path, boolean hidden) throws IOException { - FileUtil.setHidden(path, hidden); - } - - @Override - public String readSymLink(File path) throws IOException { - return FileUtil.readSymlink(path); - } - - @Override - public void createSymLink(File path, String target) throws IOException { - FileUtil.createSymLink(path, target); - } - /** * @since 3.3 */ @Override public Attributes getAttributes(File path) { - return FileUtil.getFileAttributesBasic(this, path); + return FileUtils.getFileAttributesBasic(this, path); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java index 3c3b2ebd97..2450be4c17 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java @@ -44,7 +44,6 @@ package org.eclipse.jgit.util; import java.io.File; -import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; @@ -168,74 +167,6 @@ public class FS_Win32_Cygwin extends FS_Win32 { return true; } - @Override - public boolean isSymLink(File path) throws IOException { - return FileUtil.isSymlink(path); - } - - @Override - public long lastModified(File path) throws IOException { - return FileUtil.lastModified(path); - } - - @Override - public void setLastModified(File path, long time) throws IOException { - FileUtil.setLastModified(path, time); - } - - @Override - public void delete(File path) throws IOException { - FileUtil.delete(path); - } - - @Override - public long length(File f) throws IOException { - return FileUtil.getLength(f); - } - - @Override - public boolean exists(File path) { - return FileUtil.exists(path); - } - - @Override - public boolean isDirectory(File path) { - return FileUtil.isDirectory(path); - } - - @Override - public boolean isFile(File path) { - return FileUtil.isFile(path); - } - - @Override - public boolean isHidden(File path) throws IOException { - return FileUtil.isHidden(path); - } - - @Override - public void setHidden(File path, boolean hidden) throws IOException { - FileUtil.setHidden(path, hidden); - } - - @Override - public String readSymLink(File path) throws IOException { - return FileUtil.readSymlink(path); - } - - @Override - public void createSymLink(File path, String target) throws IOException { - FileUtil.createSymLink(path, target); - } - - /** - * @since 3.3 - */ - @Override - public Attributes getAttributes(File path) { - return FileUtil.getFileAttributesBasic(this, path); - } - /** * @since 3.7 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java index f5babedd06..b87b9a41d3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtil.java @@ -46,51 +46,24 @@ package org.eclipse.jgit.util; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributeView; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; -import java.nio.file.attribute.PosixFileAttributeView; -import java.nio.file.attribute.PosixFileAttributes; -import java.nio.file.attribute.PosixFilePermission; -import java.text.Normalizer; -import java.text.Normalizer.Form; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.util.FS.Attributes; /** * File utilities using Java 7 NIO2 */ +@Deprecated public class FileUtil { - static class Java7BasicAttributes extends Attributes { - - Java7BasicAttributes(FS fs, File fPath, boolean exists, - boolean isDirectory, boolean isExecutable, - boolean isSymbolicLink, boolean isRegularFile, - long creationTime, long lastModifiedTime, long length) { - super(fs, fPath, exists, isDirectory, isExecutable, isSymbolicLink, - isRegularFile, creationTime, lastModifiedTime, length); - } - } - /** * @param path * @return target path of the symlink * @throws IOException + * @deprecated use {@link FileUtils#readSymLink(File)} instead */ + @Deprecated public static String readSymlink(File path) throws IOException { - Path nioPath = path.toPath(); - Path target = Files.readSymbolicLink(nioPath); - String targetString = target.toString(); - if (SystemReader.getInstance().isWindows()) - targetString = targetString.replace('\\', '/'); - else if (SystemReader.getInstance().isMacOS()) - targetString = Normalizer.normalize(targetString, Form.NFC); - return targetString; + return FileUtils.readSymLink(path); } /** @@ -99,218 +72,168 @@ public class FileUtil { * @param target * target of the symlink to be created * @throws IOException + * @deprecated use {@link FileUtils#createSymLink(File, String)} instead */ + @Deprecated public static void createSymLink(File path, String target) throws IOException { - Path nioPath = path.toPath(); - if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) - Files.delete(nioPath); - if (SystemReader.getInstance().isWindows()) - target = target.replace('/', '\\'); - Path nioTarget = new File(target).toPath(); - Files.createSymbolicLink(nioPath, nioTarget); + FileUtils.createSymLink(path, target); } /** * @param path * @return {@code true} if the passed path is a symlink + * @deprecated Use {@link Files#isSymbolicLink(java.nio.file.Path)} instead */ + @Deprecated public static boolean isSymlink(File path) { - Path nioPath = path.toPath(); - return Files.isSymbolicLink(nioPath); + return FileUtils.isSymlink(path); } /** * @param path * @return lastModified attribute for given path * @throws IOException + * @deprecated Use + * {@link Files#getLastModifiedTime(java.nio.file.Path, java.nio.file.LinkOption...)} + * instead */ + @Deprecated public static long lastModified(File path) throws IOException { - Path nioPath = path.toPath(); - return Files.getLastModifiedTime(nioPath, LinkOption.NOFOLLOW_LINKS) - .toMillis(); + return FileUtils.lastModified(path); } /** * @param path * @param time * @throws IOException + * @deprecated Use + * {@link Files#setLastModifiedTime(java.nio.file.Path, java.nio.file.attribute.FileTime)} + * instead */ + @Deprecated public static void setLastModified(File path, long time) throws IOException { - Path nioPath = path.toPath(); - Files.setLastModifiedTime(nioPath, FileTime.fromMillis(time)); + FileUtils.setLastModified(path, time); } /** * @param path * @return {@code true} if the given path exists + * @deprecated Use + * {@link Files#exists(java.nio.file.Path, java.nio.file.LinkOption...)} + * instead */ + @Deprecated public static boolean exists(File path) { - Path nioPath = path.toPath(); - return Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS); + return FileUtils.exists(path); } /** * @param path * @return {@code true} if the given path is hidden * @throws IOException + * @deprecated Use {@link Files#isHidden(java.nio.file.Path)} instead */ + @Deprecated public static boolean isHidden(File path) throws IOException { - Path nioPath = path.toPath(); - return Files.isHidden(nioPath); + return FileUtils.isHidden(path); } /** * @param path * @param hidden * @throws IOException + * @deprecated Use {@link FileUtils#setHidden(File,boolean)} instead */ + @Deprecated public static void setHidden(File path, boolean hidden) throws IOException { - Path nioPath = path.toPath(); - Files.setAttribute(nioPath, "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$ - LinkOption.NOFOLLOW_LINKS); + FileUtils.setHidden(path, hidden); } /** * @param path * @return length of the given file * @throws IOException + * @deprecated Use {@link FileUtils#getLength(File)} instead */ + @Deprecated public static long getLength(File path) throws IOException { - Path nioPath = path.toPath(); - if (Files.isSymbolicLink(nioPath)) - return Files.readSymbolicLink(nioPath).toString() - .getBytes(Constants.CHARSET).length; - return Files.size(nioPath); + return FileUtils.getLength(path); } /** * @param path * @return {@code true} if the given file a directory + * @deprecated Use + * {@link Files#isDirectory(java.nio.file.Path, java.nio.file.LinkOption...)} + * instead */ + @Deprecated public static boolean isDirectory(File path) { - Path nioPath = path.toPath(); - return Files.isDirectory(nioPath, LinkOption.NOFOLLOW_LINKS); + return FileUtils.isDirectory(path); } /** * @param path * @return {@code true} if the given file is a file + * @deprecated Use + * {@link Files#isRegularFile(java.nio.file.Path, java.nio.file.LinkOption...)} + * instead */ + @Deprecated public static boolean isFile(File path) { - Path nioPath = path.toPath(); - return Files.isRegularFile(nioPath, LinkOption.NOFOLLOW_LINKS); + return FileUtils.isFile(path); } /** * @param path * @return {@code true} if the given file can be executed + * @deprecated Use {@link FileUtils#canExecute(File)} instead */ + @Deprecated public static boolean canExecute(File path) { - if (!isFile(path)) - return false; - return path.canExecute(); + return FileUtils.canExecute(path); } /** * @param path * @throws IOException + * @deprecated use {@link FileUtils#delete(File)} */ + @Deprecated public static void delete(File path) throws IOException { - Path nioPath = path.toPath(); - Files.delete(nioPath); - } - - static Attributes getFileAttributesBasic(FS fs, File path) { - try { - Path nioPath = path.toPath(); - BasicFileAttributes readAttributes = nioPath - .getFileSystem() - .provider() - .getFileAttributeView(nioPath, - BasicFileAttributeView.class, - LinkOption.NOFOLLOW_LINKS).readAttributes(); - Attributes attributes = new FileUtil.Java7BasicAttributes(fs, path, - true, - readAttributes.isDirectory(), - fs.supportsExecute() ? path.canExecute() : false, - readAttributes.isSymbolicLink(), - readAttributes.isRegularFile(), // - readAttributes.creationTime().toMillis(), // - readAttributes.lastModifiedTime().toMillis(), - readAttributes.isSymbolicLink() ? Constants - .encode(FileUtils.readSymLink(path)).length - : readAttributes.size()); - return attributes; - } catch (NoSuchFileException e) { - return new FileUtil.Java7BasicAttributes(fs, path, false, false, - false, false, false, 0L, 0L, 0L); - } catch (IOException e) { - return new Attributes(path, fs); - } + FileUtils.delete(path); } /** * @param fs * @param path * @return file system attributes for the given file + * @deprecated Use {@link FileUtils#getFileAttributesPosix(FS,File)} instead */ + @Deprecated public static Attributes getFileAttributesPosix(FS fs, File path) { - try { - Path nioPath = path.toPath(); - PosixFileAttributes readAttributes = nioPath - .getFileSystem() - .provider() - .getFileAttributeView(nioPath, - PosixFileAttributeView.class, - LinkOption.NOFOLLOW_LINKS).readAttributes(); - Attributes attributes = new FileUtil.Java7BasicAttributes( - fs, - path, - true, // - readAttributes.isDirectory(), // - readAttributes.permissions().contains( - PosixFilePermission.OWNER_EXECUTE), - readAttributes.isSymbolicLink(), - readAttributes.isRegularFile(), // - readAttributes.creationTime().toMillis(), // - readAttributes.lastModifiedTime().toMillis(), - readAttributes.size()); - return attributes; - } catch (NoSuchFileException e) { - return new FileUtil.Java7BasicAttributes(fs, path, false, false, - false, false, false, 0L, 0L, 0L); - } catch (IOException e) { - return new Attributes(path, fs); - } + return FileUtils.getFileAttributesPosix(fs, path); } /** * @param file * @return on Mac: NFC normalized {@link File}, otherwise the passed file + * @deprecated Use {@link FileUtils#normalize(File)} instead */ + @Deprecated public static File normalize(File file) { - if (SystemReader.getInstance().isMacOS()) { - // TODO: Would it be faster to check with isNormalized first - // assuming normalized paths are much more common - String normalized = Normalizer.normalize(file.getPath(), - Normalizer.Form.NFC); - return new File(normalized); - } - return file; + return FileUtils.normalize(file); } /** * @param name * @return on Mac: NFC normalized form of given name + * @deprecated Use {@link FileUtils#normalize(String)} instead */ + @Deprecated public static String normalize(String name) { - if (SystemReader.getInstance().isMacOS()) { - if (name == null) - return null; - return Normalizer.normalize(name, Normalizer.Form.NFC); - } - return name; + return FileUtils.normalize(name); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 1e58245ea7..548d239c8d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -48,12 +48,28 @@ package org.eclipse.jgit.util; import java.io.File; import java.io.IOException; import java.nio.channels.FileLock; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; import java.text.MessageFormat; +import java.text.Normalizer; +import java.text.Normalizer.Form; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.FS.Attributes; /** * File Utilities @@ -207,30 +223,68 @@ public class FileUtils { */ public static void rename(final File src, final File dst) throws IOException { + rename(src, dst, StandardCopyOption.REPLACE_EXISTING); + } + + /** + * Rename a file or folder using the passed {@link CopyOption}s. If the + * rename fails and if we are running on a filesystem where it makes sense + * to repeat a failing rename then repeat the rename operation up to 9 times + * with 100ms sleep time between two calls. Furthermore if the destination + * exists and is a directory hierarchy with only directories in it, the + * whole directory hierarchy will be deleted. If the target represents a + * non-empty directory structure, empty subdirectories within that structure + * may or may not be deleted even if the method fails. Furthermore if the + * destination exists and is a file then the file will be replaced if + * {@link StandardCopyOption#REPLACE_EXISTING} has been set. If + * {@link StandardCopyOption#ATOMIC_MOVE} has been set the rename will be + * done atomically or fail with an {@link AtomicMoveNotSupportedException} + * + * @param src + * the old file + * @param dst + * the new file + * @param options + * options to pass to + * {@link Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)} + * @throws AtomicMoveNotSupportedException + * if file cannot be moved as an atomic file system operation + * @throws IOException + * @since 4.1 + */ + public static void rename(final File src, final File dst, + CopyOption... options) + throws AtomicMoveNotSupportedException, IOException { int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1; while (--attempts >= 0) { - if (src.renameTo(dst)) - return; try { - if (!dst.delete()) - delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE); - // On *nix there is no try, you do or do not - if (src.renameTo(dst)) - return; + Files.move(src.toPath(), dst.toPath(), options); + return; + } catch (AtomicMoveNotSupportedException e) { + throw e; } catch (IOException e) { - // ignore and continue retry + try { + if (!dst.delete()) { + delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE); + } + // On *nix there is no try, you do or do not + Files.move(src.toPath(), dst.toPath(), options); + return; + } catch (IOException e2) { + // ignore and continue retry + } } try { Thread.sleep(100); } catch (InterruptedException e) { - throw new IOException(MessageFormat.format( - JGitText.get().renameFileFailed, src.getAbsolutePath(), - dst.getAbsolutePath())); + throw new IOException( + MessageFormat.format(JGitText.get().renameFileFailed, + src.getAbsolutePath(), dst.getAbsolutePath())); } } - throw new IOException(MessageFormat.format( - JGitText.get().renameFileFailed, src.getAbsolutePath(), - dst.getAbsolutePath())); + throw new IOException( + MessageFormat.format(JGitText.get().renameFileFailed, + src.getAbsolutePath(), dst.getAbsolutePath())); } /** @@ -350,18 +404,33 @@ public class FileUtils { */ public static void createSymLink(File path, String target) throws IOException { - FS.DETECTED.createSymLink(path, target); + Path nioPath = path.toPath(); + if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) { + Files.delete(nioPath); + } + if (SystemReader.getInstance().isWindows()) { + target = target.replace('/', '\\'); + } + Path nioTarget = new File(target).toPath(); + Files.createSymbolicLink(nioPath, nioTarget); } /** * @param path - * @return the target of the symbolic link, or null if it is not a symbolic - * link + * @return target path of the symlink, or null if it is not a symbolic link * @throws IOException * @since 3.0 */ public static String readSymLink(File path) throws IOException { - return FS.DETECTED.readSymLink(path); + Path nioPath = path.toPath(); + Path target = Files.readSymbolicLink(nioPath); + String targetString = target.toString(); + if (SystemReader.getInstance().isWindows()) { + targetString = targetString.replace('\\', '/'); + } else if (SystemReader.getInstance().isMacOS()) { + targetString = Normalizer.normalize(targetString, Form.NFC); + } + return targetString; } /** @@ -453,4 +522,212 @@ public class FileUtils { } return builder.toString(); } + + /** + * Determine if an IOException is a Stale NFS File Handle + * + * @param ioe + * @return a boolean true if the IOException is a Stale NFS FIle Handle + * @since 4.1 + */ + public static boolean isStaleFileHandle(IOException ioe) { + String msg = ioe.getMessage(); + return msg != null + && msg.toLowerCase().matches("stale .*file .*handle"); //$NON-NLS-1$ + } + + /** + * @param file + * @return {@code true} if the passed file is a symbolic link + */ + static boolean isSymlink(File file) { + return Files.isSymbolicLink(file.toPath()); + } + + /** + * @param file + * @return lastModified attribute for given file, not following symbolic + * links + * @throws IOException + */ + static long lastModified(File file) throws IOException { + return Files.getLastModifiedTime(file.toPath(), LinkOption.NOFOLLOW_LINKS) + .toMillis(); + } + + /** + * @param file + * @param time + * @throws IOException + */ + static void setLastModified(File file, long time) throws IOException { + Files.setLastModifiedTime(file.toPath(), FileTime.fromMillis(time)); + } + + /** + * @param file + * @return {@code true} if the given file exists, not following symbolic + * links + */ + static boolean exists(File file) { + return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS); + } + + /** + * @param file + * @return {@code true} if the given file is hidden + * @throws IOException + */ + static boolean isHidden(File file) throws IOException { + return Files.isHidden(file.toPath()); + } + + /** + * @param file + * @param hidden + * @throws IOException + * @since 4.1 + */ + public static void setHidden(File file, boolean hidden) throws IOException { + Files.setAttribute(file.toPath(), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$ + LinkOption.NOFOLLOW_LINKS); + } + + /** + * @param file + * @return length of the given file + * @throws IOException + * @since 4.1 + */ + public static long getLength(File file) throws IOException { + Path nioPath = file.toPath(); + if (Files.isSymbolicLink(nioPath)) + return Files.readSymbolicLink(nioPath).toString() + .getBytes(Constants.CHARSET).length; + return Files.size(nioPath); + } + + /** + * @param file + * @return {@code true} if the given file is a directory, not following + * symbolic links + */ + static boolean isDirectory(File file) { + return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS); + } + + /** + * @param file + * @return {@code true} if the given file is a file, not following symbolic + * links + */ + static boolean isFile(File file) { + return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS); + } + + /** + * @param file + * @return {@code true} if the given file can be executed + * @since 4.1 + */ + public static boolean canExecute(File file) { + if (!isFile(file)) { + return false; + } + return Files.isExecutable(file.toPath()); + } + + /** + * @param fs + * @param file + * @return non null attributes object + */ + static Attributes getFileAttributesBasic(FS fs, File file) { + try { + Path nioPath = file.toPath(); + BasicFileAttributes readAttributes = nioPath + .getFileSystem() + .provider() + .getFileAttributeView(nioPath, + BasicFileAttributeView.class, + LinkOption.NOFOLLOW_LINKS).readAttributes(); + Attributes attributes = new Attributes(fs, file, + true, + readAttributes.isDirectory(), + fs.supportsExecute() ? file.canExecute() : false, + readAttributes.isSymbolicLink(), + readAttributes.isRegularFile(), // + readAttributes.creationTime().toMillis(), // + readAttributes.lastModifiedTime().toMillis(), + readAttributes.isSymbolicLink() ? Constants + .encode(readSymLink(file)).length + : readAttributes.size()); + return attributes; + } catch (IOException e) { + return new Attributes(file, fs); + } + } + + /** + * @param fs + * @param file + * @return file system attributes for the given file + * @since 4.1 + */ + public static Attributes getFileAttributesPosix(FS fs, File file) { + try { + Path nioPath = file.toPath(); + PosixFileAttributes readAttributes = nioPath + .getFileSystem() + .provider() + .getFileAttributeView(nioPath, + PosixFileAttributeView.class, + LinkOption.NOFOLLOW_LINKS).readAttributes(); + Attributes attributes = new Attributes( + fs, + file, + true, // + readAttributes.isDirectory(), // + readAttributes.permissions().contains( + PosixFilePermission.OWNER_EXECUTE), + readAttributes.isSymbolicLink(), + readAttributes.isRegularFile(), // + readAttributes.creationTime().toMillis(), // + readAttributes.lastModifiedTime().toMillis(), + readAttributes.size()); + return attributes; + } catch (IOException e) { + return new Attributes(file, fs); + } + } + + /** + * @param file + * @return on Mac: NFC normalized {@link File}, otherwise the passed file + * @since 4.1 + */ + public static File normalize(File file) { + if (SystemReader.getInstance().isMacOS()) { + // TODO: Would it be faster to check with isNormalized first + // assuming normalized paths are much more common + String normalized = Normalizer.normalize(file.getPath(), + Normalizer.Form.NFC); + return new File(normalized); + } + return file; + } + + /** + * @param name + * @return on Mac: NFC normalized form of given name + * @since 4.1 + */ + public static String normalize(String name) { + if (SystemReader.getInstance().isMacOS()) { + if (name == null) + return null; + return Normalizer.normalize(name, Normalizer.Form.NFC); + } + return name; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java index c817c475aa..0d283fde63 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java @@ -51,6 +51,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.text.MessageFormat; @@ -371,6 +372,75 @@ public class IO { return l; } + /** + * Read the next line from a reader. + * <p> + * Like {@link java.io.BufferedReader#readLine()}, but only treats + * {@code \n} as end-of-line, and includes the trailing newline. + * + * @param in + * the reader to read from. + * @param sizeHint + * hint for buffer sizing; 0 or negative for default. + * @return the next line from the input, always ending in {@code \n} unless + * EOF was reached. + * @throws IOException + * there was an error reading from the stream. + * @since 4.1 + */ + public static String readLine(Reader in, int sizeHint) throws IOException { + if (in.markSupported()) { + if (sizeHint <= 0) { + sizeHint = 1024; + } + StringBuilder sb = new StringBuilder(sizeHint); + char[] buf = new char[sizeHint]; + while (true) { + in.mark(sizeHint); + int n = in.read(buf); + if (n < 0) { + in.reset(); + return sb.toString(); + } + for (int i = 0; i < n; i++) { + if (buf[i] == '\n') { + resetAndSkipFully(in, ++i); + sb.append(buf, 0, i); + return sb.toString(); + } + } + if (n > 0) { + sb.append(buf, 0, n); + } + resetAndSkipFully(in, n); + } + } else { + StringBuilder buf = sizeHint > 0 + ? new StringBuilder(sizeHint) + : new StringBuilder(); + int i; + while ((i = in.read()) != -1) { + char c = (char) i; + buf.append(c); + if (c == '\n') { + break; + } + } + return buf.toString(); + } + } + + private static void resetAndSkipFully(Reader fd, long toSkip) throws IOException { + fd.reset(); + while (toSkip > 0) { + long r = fd.skip(toSkip); + if (r <= 0) { + throw new EOFException(JGitText.get().shortSkipOfBlock); + } + toSkip -= r; + } + } + private IO() { // Don't create instances of a static only utility. } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java index 3c2460cad7..45c339fb48 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java @@ -390,7 +390,28 @@ public final class RawParseUtils { * @return the timezone at this location, expressed in minutes. */ public static final int parseTimeZoneOffset(final byte[] b, int ptr) { - final int v = parseBase10(b, ptr, null); + return parseTimeZoneOffset(b, ptr, null); + } + + /** + * Parse a Git style timezone string. + * <p> + * The sequence "-0315" will be parsed as the numeric value -195, as the + * lower two positions count minutes, not 100ths of an hour. + * + * @param b + * buffer to scan. + * @param ptr + * position within buffer to start parsing digits at. + * @param ptrResult + * optional location to return the new ptr value through. If null + * the ptr value will be discarded. + * @return the timezone at this location, expressed in minutes. + * @since 4.1 + */ + public static final int parseTimeZoneOffset(final byte[] b, int ptr, + MutableInteger ptrResult) { + final int v = parseBase10(b, ptr, ptrResult); final int tzMins = v % 100; final int tzHours = v / 100; return tzHours * 60 + tzMins; @@ -1081,7 +1102,17 @@ public final class RawParseUtils { return ptr; } - private static int lastIndexOfTrim(byte[] raw, char ch, int pos) { + /** + * @param raw + * buffer to scan. + * @param ch + * character to find. + * @param pos + * starting position. + * @return last index of ch in raw, trimming spaces. + * @since 4.1 + */ + public static int lastIndexOfTrim(byte[] raw, char ch, int pos) { while (pos >= 0 && raw[pos] == ' ') pos--; 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 b4233b6ccd..4795c89e73 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -71,6 +71,11 @@ import org.eclipse.jgit.lib.ObjectChecker; */ public abstract class SystemReader { private static final SystemReader DEFAULT; + + private static Boolean isMacOS; + + private static Boolean isWindows; + static { SystemReader r = new Default(); r.init(); @@ -148,6 +153,8 @@ public abstract class SystemReader { * the default instance. */ public static void setInstance(SystemReader newReader) { + isMacOS = null; + isWindows = null; if (newReader == null) INSTANCE = DEFAULT; else { @@ -293,26 +300,31 @@ public abstract class SystemReader { * @return true if we are running on a Windows. */ public boolean isWindows() { - String osDotName = AccessController - .doPrivileged(new PrivilegedAction<String>() { - public String run() { - return getProperty("os.name"); //$NON-NLS-1$ - } - }); - return osDotName.startsWith("Windows"); //$NON-NLS-1$ + if (isWindows == null) { + String osDotName = getOsName(); + isWindows = Boolean.valueOf(osDotName.startsWith("Windows")); //$NON-NLS-1$ + } + return isWindows.booleanValue(); } /** * @return true if we are running on Mac OS X */ public boolean isMacOS() { - String osDotName = AccessController - .doPrivileged(new PrivilegedAction<String>() { - public String run() { - return getProperty("os.name"); //$NON-NLS-1$ - } - }); - return "Mac OS X".equals(osDotName) || "Darwin".equals(osDotName); //$NON-NLS-1$ //$NON-NLS-2$ + if (isMacOS == null) { + String osDotName = getOsName(); + isMacOS = Boolean.valueOf( + "Mac OS X".equals(osDotName) || "Darwin".equals(osDotName)); //$NON-NLS-1$ //$NON-NLS-2$ + } + return isMacOS.booleanValue(); + } + + private String getOsName() { + return AccessController.doPrivileged(new PrivilegedAction<String>() { + public String run() { + return getProperty("os.name"); //$NON-NLS-1$ + } + }); } /** @@ -51,7 +51,7 @@ <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> <packaging>pom</packaging> - <version>4.0.3.201509231615-r</version> + <version>4.1.2.201602141800-r</version> <name>JGit - Parent</name> <url>${jgit-url}</url> @@ -192,7 +192,7 @@ <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format> <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest> - <jgit-last-release-version>3.7.0.201502260915-r</jgit-last-release-version> + <jgit-last-release-version>4.0.0.201506090130-r</jgit-last-release-version> <jsch-version>0.1.53</jsch-version> <javaewah-version>0.7.9</javaewah-version> <junit-version>4.11</junit-version> @@ -201,7 +201,7 @@ <commons-compress-version>1.6</commons-compress-version> <osgi-core-version>4.3.1</osgi-core-version> <servlet-api-version>3.1.0</servlet-api-version> - <jetty-version>9.2.10.v20150310</jetty-version> + <jetty-version>9.2.13.v20150730</jetty-version> <clirr-version>2.6.1</clirr-version> <httpclient-version>4.3.6</httpclient-version> <slf4j-version>1.7.2</slf4j-version> |